Flutter完整開發實戰詳解(三、 打包與填坑篇)

作爲系列文章的第三篇,繼篇章一篇章二之後,本篇將爲你着重展示:Flutter開發過程的打包流程、APP包對比、細節技巧與問題處理。本篇主要描述的Flutter的打包、在開發過程中遇到的各類問題與細節,算是對上兩篇的補全。

 友情提示:本文所有代碼均在 GSYGithubAppFlutter ,要不試試?(◐‿◑)。

一、打包

首先我們先看結果,如下表所示,是 Flutter 與 React Native 、IOS 與 Android 的縱向與橫向對比

項目 IOS Android
GSYGithubAppFlutter flutter-ipa flutter-apk
GSYGithubAppRN rn-ipa rn-apk

從上表我們可以看到:
* Fluuter的 apk 會比 ipa 更小一些,這其中的一部分原因是 Flutter 使用的 Skia 在Android 上是自帶的。
* 橫向對比 React Native ,雖然項目不完全一樣,但是大部分功能一致的情況下, Flutter 的 Apk 確實更小一些。這裏又有一個細節,rn 的 ipa 包體積小很多,這其實是因爲 javascriptcore 在 ios上 是內置的原因。

1、Android打包

I'm Android

在Android的打包上,筆者基本沒有遇到什麼問題,在android/app/build.grade文件下,配置applicationIdversionCodeversionName 和簽名信息,最後通過 flutter build app 即可完成編譯。編程成功的包在 build/app/outputs/apk/release 下。

2、IOS打包與真機運行

在IOS的打包上,筆者倒是經歷了一波曲折,這裏主要講筆者遇到的問題。

首先你需要一個 apple 開發者賬號,然後創建證書、創建AppId,創建配置文件、最後在info.plist文件下輸入相關信息,更詳細可看官方的《發佈的IOS版APP》的教程。

但由於筆者項目中使用了第三方的插件包如 shared_preferences 等,在執行 Archive 的過程卻一直出現如下問題:

在 `Archive` 時提示找不到
#import <connectivity/ConnectivityPlugin.h>  ///file not found
#import <device_info/DeviceInfoPlugin.h>
#import <flutter_statusbar/FlutterStatusbarPlugin.h>
#import <flutter_webview_plugin/FlutterWebviewPlugin.h>
#import <fluttertoast/FluttertoastPlugin.h>
#import <get_version/GetVersionPlugin.h>
#import <package_info/PackageInfoPlugin.h>
#import <share/SharePlugin.h>
#import <shared_preferences/SharedPreferencesPlugin.h>
#import <sqflite/SqflitePlugin.h>
#import <url_launcher/UrlLauncherPlugin.h>

通過 Android Studio 運行到 IOS 模擬器時沒有任何問題,說明這不是第三方包問題。通過查找問題發現,在 IOS 執行 Archive 之前,需要執行 flutter build release,如下圖在命令執行之後,Pod 的執行目錄會發現改變,並且生成打包需要的文件。(ps 普通運行時自動又會修改回來

文件變化
 

但是實際在執行 flutter build release 後,問題依然存在,最終翻山越嶺(╯‵□′)╯︵┻━┻,終於找到兩個答案:

  • Issue#19241 下描述了類似問題,但是他們因爲路徑問題導致,經過嘗試並不能解決。

  • Issue#18305 真實的解決了這個問題,居然是因爲 Pod 的工程沒引入:

open ios/Runner.xcodeproj

I checked Runner/Pods is empty in Xcode sidebar.

drop Pods/Pods.xcodeproj into Runner/Pods.

"Valid architectures" to only "arm64" (I removed armv7 armv7s) 

最後終於成功打包,心累啊(///▽///)。同時如果希望直接在真機上調試 Flutter,可以參考 :《Flutter基礎—開發環境與入門》 下的 IOS 真機部分。

二、細節

這裏主要講一些小細節

1、AppBar

在 Flutter 中 AppBar 算是常用 Widget ,而 AppBar 可不僅僅作爲標題欄和使用,AppBar上的 leadingbottom 同樣是有用的功能。

  • AppBar 的 bottom 默認支持 TabBar, 也就是常見的頂部 Tab 的效果,這其實是因爲TabBar 實現了 PreferredSizeWidgetpreferredSize
    所以只要你的控件實現了 preferredSize,就可以放到 AppBar 的 bottom 中使用。比如下圖搜索欄,這是TabView下的頁面又實用了AppBar。

  • leading :通常是左側按鍵,不設置時一般是 Drawer 的圖標或者返回按鈕。

  • flexibleSpace :位於 bottomleading 之間。

2、按鍵

Flutter 中的按鍵,如 FlatButton 默認是否有邊距和最小大小的。所以如果你想要無 padding、margin、border 、默認大小 等的按鍵效果,其中一種方式如下:

///
new RawMaterialButton(
        materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
        padding: padding ?? const EdgeInsets.all(0.0),
        constraints: const BoxConstraints(minWidth: 0.0, minHeight: 0.0),
        child: child,
        onPressed: onPressed);

如果在再上 Flex ,如下所示,一個可控的填充按鍵就出來了。

new RawMaterialButton(
        materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
        padding: padding ?? const EdgeInsets.all(0.0),
        constraints: const BoxConstraints(minWidth: 0.0, minHeight: 0.0),
        ///flex
        child: new Flex(
          mainAxisAlignment: mainAxisAlignment,
          direction: Axis.horizontal,
          children: <Widget>[],
        ),
        onPressed: onPressed);

3、StatefulWidget 賦值

這裏我們以給 TextField 主動賦值爲例,其實 Flutter 中,給有狀態的 Widget 傳遞狀態或者數據,一般都是通過各種 controller 。如 TextField 的主動賦值,如下代碼所示:


 final TextEditingController controller = new TextEditingController();

 @override
 void didChangeDependencies() {
    super.didChangeDependencies();
    ///通過給 controller 的 value 新創建一個 TextEditingValue
    controller.value = new TextEditingValue(text: "給輸入框填入參數");
 }

 @override
  Widget build(BuildContext context) {
    return new TextField(
     ///controller
      controller: controller,
      onChanged: onChanged,
      obscureText: obscureText,
      decoration: new InputDecoration(
        hintText: hintText,
        icon: iconData == null ? null : new Icon(iconData),
      ),
    );
  }

其實 TextEditingValueValueNotifier,其中 value 的 setter 方法被重載,一旦改變就會觸發 notifyListeners 方法。而 TextEditingController 中,通過調用 addListener 就監聽了數據的改變,從而讓UI更新。

當然,賦值有更簡單粗暴的做法是:傳遞一個對象 class A 對象,在控件內部使用對象 A.b 的變量綁定控件,外部通過 setState({ A.b = b2}) 更新

4、GlobalKey

在Flutter中,要主動改變子控件的狀態,還可以使用 GlobalKey。 比如你需要主動調用 RefreshIndicator 顯示刷新狀態,如下代碼所示。


 GlobalKey<RefreshIndicatorState> refreshIndicatorKey;

 showForRefresh() {
    ///顯示刷新
    refreshIndicatorKey.currentState.show();
  }

  @override
  Widget build(BuildContext context) {
    refreshIndicatorKey =  new GlobalKey<RefreshIndicatorState>();
    return new RefreshIndicator(
      key: refreshIndicatorKey,
      onRefresh: onRefresh,
      child: new ListView.builder(
        ///·····
      ),
    );
  }

5、Redux 與主題

使用 Redux 來做 Flutter 的全局 State 管理最合適不過,由於Redux內容較多,如果感興趣的可以看看 篇章二 ,這裏主要通過 Redux 來實現實時切換主題的效果。

如下代碼,通過 StoreProvider 加載了 store ,再通過 StoreBuilder 將 store 中的 themeData 綁定到 MaterialApp 的 theme 下,之後在其他 Widget 中通過 Theme.of(context) 調你需要的顏色,最終在任意位置調用 store.dispatch 就可實時修改主題,效果如後圖所示。

class FlutterReduxApp extends StatelessWidget {
  final store = new Store<GSYState>(
    appReducer,
    initialState: new GSYState(
      themeData: new ThemeData(
        primarySwatch: GSYColors.primarySwatch,
      ),
    ),
  );

  FlutterReduxApp({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    /// 通過 StoreProvider 應用 store
    return new StoreProvider(
      store: store,
      ///通過 StoreBuilder 獲取 themeData
      child: new StoreBuilder<GSYState>(builder: (context, store) {
        return new MaterialApp(
            theme: store.state.themeData,
            routes: {
              HomePage.sName: (context) {
                return HomePage();
              },
            });
      }),
    );
  }
}

主題

6、Hotload 與 Package

Flutter 在 Debug 和 Release 下分別是 JITAOT 模式,而在 DEBUG 下,是支持 Hotload 的,而且十分絲滑。但是需要注意的是:如果開發過程中安裝了新的第三方包 ,而新的第三方包如果包含了原生代碼,需要停止後重新運行哦。

pubspec.yaml 文件下就是我們的包依賴目錄,其中 ^ 代表大於等於,一般情況下 upgradeget 都能達到下載包的作用。但是:upgrade 會在包有更新的情況下,更新 pubspec.lock 文件下包的版本

三、問題處理

  • Waiting for another flutter command to release the startup lock :如果遇到這個問題:
  1、打開flutter的安裝目錄/bin/cache/ 
  2、刪除lockfile文件 
  3、重啓AndroidStudio
  • dialog下的黃色線
    yellow-lines-under-text-widgets-in-flutter:showDialog 中,默認是沒使用 Scaffold ,這回導致文本有黃色溢出線提示,可以使用 Material 包一層處理。

  • TabBar + TabView + KeepAlive 的問題
    可以通過 TabBar + PageView 解決,具體可見 篇章二

自此,第三篇終於結束了!(///▽///)

資源推薦

完整開源項目推薦:
文章

《Flutter完整開發實戰詳解(一、Dart語言和Flutter基礎)》

《Flutter完整開發實戰詳解(二、 快速開發實戰篇)》

《跨平臺項目開源項目推薦》

《移動端跨平臺開發的深度解析》

我們還會再見嗎?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章