作為系列文章的第三篇,本篇將為你著重展示:Flutter開(kāi)發(fā)過(guò)程的打包流程奖蔓、APP包對(duì)比赞草、細(xì)節(jié)技巧與問(wèn)題處理,本篇主要描述的 Flutter 的打包吆鹤、在開(kāi)發(fā)過(guò)程中遇到的各類(lèi)問(wèn)題與細(xì)節(jié)厨疙,算是對(duì)上兩篇的補(bǔ)全。
文章匯總地址:
一疑务、打包
首先我們先看結(jié)果沾凄,如下表所示,是 Flutter 與 React Native 知允、iOS 與 Android 的縱向與橫向?qū)Ρ?/strong> 撒蟀。
項(xiàng)目 | IOS | Android |
---|---|---|
GSYGithubAppFlutter | flutter-ipa
|
flutter-apk
|
GSYGithubAppRN | rn-ipa
|
rn-apk
|
從上表我們可以看到:
Fluuter的 apk 會(huì)比 ipa 更小一些,這其中的一部分原因是 Flutter 使用的
Skia
在Android 上是自帶的温鸽。橫向?qū)Ρ?React Native 保屯,雖然項(xiàng)目不完全一樣,但是大部分功能一致的情況下涤垫, Flutter 的 Apk 確實(shí)更小一些姑尺。這里又有一個(gè)細(xì)節(jié),rn 的 ipa 包體積小很多蝠猬,這其實(shí)是因?yàn)?
javascriptcore
在 ios上 是內(nèi)置的原因股缸。對(duì)上述內(nèi)容有興趣的可以看看《移動(dòng)端跨平臺(tái)開(kāi)發(fā)的深度解析》。
1吱雏、Android 打包
在 Android 的打包上敦姻,筆者基本沒(méi)有遇到什么問(wèn)題瘾境,在android/app/build.grade
文件下,配置applicationId
镰惦、versionCode
迷守、versionName
和簽名信息,最后通過(guò) flutter build app
即可完成編譯旺入。編程成功的包在 build/app/outputs/apk/release
下兑凿。
2、iOS 打包與真機(jī)運(yùn)行
在 iOS 的打包上茵瘾,筆者倒是經(jīng)歷了一波曲折礼华,這里主要講筆者遇到的問(wèn)題。
首先你需要一個(gè) apple 開(kāi)發(fā)者賬號(hào)拗秘,然后創(chuàng)建證書(shū)圣絮、創(chuàng)建AppId,創(chuàng)建配置文件雕旨、最后在info.plist
文件下輸入相關(guān)信息扮匠,更詳細(xì)可看官方的《發(fā)布的IOS版APP》的教程。
但由于筆者項(xiàng)目中使用了第三方的插件包如 shared_preferences
等凡涩,在執(zhí)行 Archive
的過(guò)程卻一直出現(xiàn)如下問(wèn)題:
在 `Archive` 時(shí)提示找不到
#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>
通過(guò) Android Studio 運(yùn)行到 iOS 模擬器時(shí)沒(méi)有任何問(wèn)題棒搜,說(shuō)明這不是第三方包問(wèn)題。通過(guò)查找問(wèn)題發(fā)現(xiàn)活箕,在 iOS 執(zhí)行 Archive
之前力麸,需要執(zhí)行 flutter build release
,如下圖在命令執(zhí)行之后育韩,Pod 的執(zhí)行目錄會(huì)發(fā)現(xiàn)改變克蚂,并且生成打包需要的文件。(ps 普通運(yùn)行時(shí)自動(dòng)又會(huì)修改回來(lái))
但是實(shí)際在執(zhí)行 flutter build release
后座慰,問(wèn)題依然存在,最終翻山越嶺(╯‵□′)╯︵┻━┻翠拣,終于找到兩個(gè)答案:
Issue#19241 下描述了類(lèi)似問(wèn)題版仔,但是他們因?yàn)槁窂絾?wèn)題導(dǎo)致,經(jīng)過(guò)嘗試并不能解決误墓。
Issue#18305 真實(shí)的解決了這個(gè)問(wèn)題蛮粮,居然是因?yàn)?Pod 的工程沒(méi)引入:
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)
最后終于成功打包,心累啊(//////)谜慌。同時(shí)如果希望直接在真機(jī)上調(diào)試 Flutter然想,可以參考 :《Flutter基礎(chǔ)—開(kāi)發(fā)環(huán)境與入門(mén)》 下的 iOS 真機(jī)部分。
二欣范、細(xì)節(jié)
這里主要講一些小細(xì)節(jié)
1变泄、AppBar
在 Flutter 中 AppBar 算是常用 Widget 令哟,而 AppBar 可不僅僅作為標(biāo)題欄和使用,AppBar上的 leading
和 bottom
同樣是有用的功能妨蛹。
- AppBar 的
bottom
默認(rèn)支持TabBar
, 也就是常見(jiàn)的頂部 Tab 的效果屏富,這其實(shí)是因?yàn)?code>TabBar 實(shí)現(xiàn)了PreferredSizeWidget
的preferredSize
。
所以只要你的控件實(shí)現(xiàn)了preferredSize
蛙卤,就可以放到 AppBar 的bottom
中使用狠半。比如下圖搜索欄,這是TabView下的頁(yè)面又實(shí)用了AppBar颤难。
leading
:通常是左側(cè)按鍵神年,不設(shè)置時(shí)一般是 Drawer 的圖標(biāo)或者返回按鈕。flexibleSpace
:位于bottom
和leading
之間行嗤。
2已日、按鍵
Flutter 中的按鍵,如 FlatButton
默認(rèn)是否有邊距和最小大小的昂验。所以如果你想要無(wú) padding捂敌、margin、border 既琴、默認(rèn)大小 等的按鍵效果占婉,其中一種方式如下:
///
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 ,如下所示甫恩,一個(gè)可控的填充按鍵就出來(lái)了逆济。
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
主動(dòng)賦值為例磺箕,其實(shí) Flutter 中奖慌,給有狀態(tài)的 Widget 傳遞狀態(tài)或者數(shù)據(jù),一般都是通過(guò)各種 controller 松靡。如 TextField
的主動(dòng)賦值简僧,如下代碼所示:
final TextEditingController controller = new TextEditingController();
@override
void didChangeDependencies() {
super.didChangeDependencies();
///通過(guò)給 controller 的 value 新創(chuàng)建一個(gè) TextEditingValue
controller.value = new TextEditingValue(text: "給輸入框填入?yún)?shù)");
}
@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),
),
);
}
其實(shí) TextEditingValue
是 ValueNotifier
,其中 value
的 setter 方法被重載雕欺,一旦改變就會(huì)觸發(fā) notifyListeners
方法岛马。而 TextEditingController
中,通過(guò)調(diào)用 addListener
就監(jiān)聽(tīng)了數(shù)據(jù)的改變屠列,從而讓UI更新啦逆。
當(dāng)然,賦值有更簡(jiǎn)單粗暴的做法是:傳遞一個(gè)對(duì)象 class A 對(duì)象笛洛,在控件內(nèi)部使用對(duì)象 A.b 的變量綁定控件夏志,外部通過(guò) setState({ A.b = b2}) 更新。
4苛让、GlobalKey
在Flutter中沟蔑,要主動(dòng)改變子控件的狀態(tài)湿诊,還可以使用 GlobalKey
。 比如你需要主動(dòng)調(diào)用 RefreshIndicator
顯示刷新?tīng)顟B(tài)溉贿,如下代碼所示枫吧。
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 來(lái)做 Flutter 的全局 State 管理最合適不過(guò)宇色,由于Redux內(nèi)容較多九杂,如果感興趣的可以看看 篇章二 ,這里主要通過(guò) Redux 來(lái)實(shí)現(xiàn)實(shí)時(shí)切換主題的效果宣蠕。
如下代碼例隆,通過(guò) StoreProvider
加載了 store ,再通過(guò) StoreBuilder
將 store 中的 themeData 綁定到 MaterialApp
的 theme 下抢蚀,之后在其他 Widget 中通過(guò) Theme.of(context)
調(diào)你需要的顏色镀层,最終在任意位置調(diào)用 store.dispatch
就可實(shí)時(shí)修改主題,效果如后圖所示皿曲。
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) {
/// 通過(guò) StoreProvider 應(yīng)用 store
return new StoreProvider(
store: store,
///通過(guò) 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 下分別是 JIT 和 AOT 模式,而在 DEBUG 下屋休,是支持 Hotload 的坞古,而且十分絲滑。但是需要注意的是:如果開(kāi)發(fā)過(guò)程中安裝了新的第三方包 劫樟,而新的第三方包如果包含了原生代碼痪枫,需要停止后重新運(yùn)行哦。
pubspec.yaml
文件下就是我們的包依賴(lài)目錄叠艳,其中 ^
代表大于等于奶陈,一般情況下 upgrade
和 get
都能達(dá)到下載包的作用。但是:upgrade 會(huì)在包有更新的情況下附较,更新 pubspec.lock
文件下包的版本 吃粒。
三、問(wèn)題處理
-
Waiting for another flutter command to release the startup lock
:如果遇到這個(gè)問(wèn)題:
1拒课、打開(kāi)flutter的安裝目錄/bin/cache/
2徐勃、刪除lockfile文件
3、重啟AndroidStudio
dialog下的黃色線(xiàn)
yellow-lines-under-text-widgets-in-flutter:showDialog 中捕发,默認(rèn)是沒(méi)使用 Scaffold 疏旨,這回導(dǎo)致文本有黃色溢出線(xiàn)提示很魂,可以使用 Material 包一層處理扎酷。TabBar + TabView + KeepAlive 的問(wèn)題
可以通過(guò) TabBar + PageView 解決,具體可見(jiàn) 篇章二遏匆。
自此法挨,第三篇終于結(jié)束了谁榜!(//////)
資源推薦
- Github : https://github.com/CarGuo/
- 開(kāi)源 Flutter 完整項(xiàng)目:https://github.com/CarGuo/GSYGithubAppFlutter
- 開(kāi)源 Flutter 多案例學(xué)習(xí)型項(xiàng)目: https://github.com/CarGuo/GSYFlutterDemo
- 開(kāi)源 Fluttre 實(shí)戰(zhàn)電子書(shū)項(xiàng)目:https://github.com/CarGuo/GSYFlutterBook