Flutter 混合開發(fā)
混合開發(fā)簡介
使用 Flutter 從零開始開發(fā)App是一件輕松愜意的事情,但對于一些成熟的產(chǎn)品來說,完全摒棄原有 App 的歷史沉淀,全面轉(zhuǎn)向 Flutter 是不現(xiàn)實的。因此使用 Flutter 去統(tǒng)一 Android、iOS 技術棧,把它作為已有原生 App 的擴展能力,通過有序推進來提升移動終端的開發(fā)效率。
目前,想要在已有的原生 App 里嵌入一些 Flutter 頁面主要有兩種方案。一種是將原生工程作為 Flutter 工程的子工程,由 Flutter 進行統(tǒng)一管理,這種模式稱為統(tǒng)一管理模式。另一種是將 Flutter 工程作為原生工程的子模塊,維持原有的原生工程管理方式不變,這種模式被稱為三端分離模式。

在 Flutter 框架出現(xiàn)早期,由于官方提供的混編方式以及資料有限,國內(nèi)較早使用 Flutter 進行混合開發(fā)的團隊大多使用的是統(tǒng)一管理模式。但是,隨著業(yè)務迭代的深入,統(tǒng)一管理模式的弊端也隨之顯露,不僅三端(Android、iOS和Flutter)代碼耦合嚴重,相關工具鏈耗時也隨之大幅增長,最終導致開發(fā)效率降低。所以,后續(xù)使用 Flutter 進行混合開發(fā)的團隊大多使用三端代碼分離的模式來進行依賴治理,最終實現(xiàn) Flutter 工程的輕量級接入。
除了可以輕量級接入外,三端代碼分離模式還可以把 Flutter 模塊作為原生工程的子模塊,從而快速地接入 Flutter 模塊,降低原生工程的改造成本。在完成對 Flutter 模塊的接入后,F(xiàn)lutter 工程可以使用 Android Studio 進行開發(fā),無需再打開原生工程就可以對 Dart 代碼和原生代碼進行開發(fā)調(diào)試。
使用三端分離模式進行 Flutter 混合開發(fā)的關鍵是抽離 Flutter 工程,將不同平臺的構(gòu)建產(chǎn)物依照標準組件化的形式進行管理,即 Android 使用 aar、iOS 使用 pod。也就是說,F(xiàn)lutter 的混編方案其實就是將 Flutter 模塊打包成 aar 或者 pod 庫,然后在原生工程像引用其他第三方原生組件庫那樣引入 Flutter 模塊即可。
Flutter 模塊
默認情況下,新創(chuàng)建的 Flutter 工程會包含 Flutter 目錄和原生工程的目錄。在這種情況下,原生工程會依賴 Flutter 工程的庫和資源,并且無法脫離 Flutter 工程獨立構(gòu)建和運行。
在混合開發(fā)中,原生工程對 Flutter 的依賴主要分為兩部分。一個是 Flutter 的庫和引擎,主要包含 Flutter 的 Framework 庫和引擎庫;另一個是 Flutter 模塊工程,即 Flutter 混合開發(fā)中的 Flutter 功能模塊,主要包括 Flutter 工程 lib 目錄下的 Dart 代碼實現(xiàn)。
對于原生工程來說,集成 Flutter 只需要在同級目錄創(chuàng)建一個 Flutter 模塊,然后構(gòu)建 iOS 和 Android 各自的 Flutter 依賴庫即可。接下來,我們只需要在原生項目的同級目錄下,執(zhí)行 Flutter 提供的構(gòu)建模塊命令創(chuàng)建 Flutter 模塊即可,如下所示。
flutter?create?-t?module?flutter_library
其中,flutter_library 為 Flutter 模塊名。執(zhí)行上面的命令后,會在原生工程的同級目錄下生成一個 flutter_library 模塊工程。Flutter 模塊也是 Flutter 工程,使用 Android Studio 打開它,其目錄如下圖所示。

可以看到,和普通的 Flutter 工程相比,F(xiàn)lutter 模塊工程也內(nèi)嵌了 Android 工程和 iOS 工程,只不過默認情況下,Android 工程和 iOS 工程是隱藏的。因此,對于 Flutter 模塊工程來說,也可以像普通工程一樣使用 Android Studio 進行開發(fā)和調(diào)試。
同時,相比普通的 Flutter 工程,F(xiàn)lutter 模塊工程的 Android 工程目錄下多了一個 Flutter 目錄,此目錄下的 build.gradle 配置就是我們構(gòu)建 aar 時的打包配置。同樣,在 Flutter 模塊工程的 iOS 工程目錄下也會找到一個 Flutter 目錄,這也是 Flutter 模塊工程既能像 Flutter 普通工程一樣使用 Android Studio 進行開發(fā)調(diào)試,又能打包構(gòu)建 aar 或 pod 的原因。
Android 集成 Flutter
在原生 Android 工程中集成 Flutter,原生工程對 Flutter 的依賴主要包括兩部分,分別是 Flutter 庫和引擎,以及 Flutter 工程構(gòu)建產(chǎn)物。
Flutter 庫和引擎:包含 icudtl.dat、libFlutter.so 以及一些 class 文件,最終這些文件都會被封裝到 Flutter.jar 中。 Flutter 工程產(chǎn)物:包括應用程序數(shù)據(jù)段 isolate_snapshot_data、應用程序指令段 isolate_snapshot_instr、虛擬機數(shù)據(jù)段 vm_snapshot_data、虛擬機指令段 vm_snapshot_instr 以及資源文件 flutter_assets。
setBinding(new?Binding([gradle:?this]))
evaluate(new?File(
??settingsDir.parentFile,
??'flutter_library/.android/include_flutter.groovy'))
dependencies?{
????implementation?project(":flutter")
}
如果出現(xiàn)“程序包 android.support.annotation 不存在”的錯誤,需要使用如下的命令來創(chuàng)建 Flutter 模塊,因為最新版本的 Android 默認使用 androidx 來管理包。
flutter?create?--androidx?-t?module?flutter_library
在原生 Android 工程中成功添加 Flutter 模塊依賴后,打開原生 Android 工程,并在應用的入口 MainActivity 文件中添加如下代碼。
public?class?MainActivity?extends?AppCompatActivity?{
????@Override
????protected?void?onCreate(Bundle?savedInstanceState)?{
????????super.onCreate(savedInstanceState);
????????View?flutterView?=?Flutter.createView(this,?getLifecycle(),?"route1");
????????FrameLayout.LayoutParams?layoutParams?=?new?FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,?ViewGroup.LayoutParams.MATCH_PARENT);
????????addContentView(flutterView,?layoutParams);
????}
}

public?class?MainActivity?extends?AppCompatActivity?{
????@Override
????protected?void?onCreate(Bundle?savedInstanceState)?{
????????super.onCreate(savedInstanceState);
????????setContentView(R.layout.activity_main);
????????FragmentTransaction?ft=?getSupportFragmentManager().beginTransaction();
????????ft.replace(R.id.fragment_container,?Flutter.createFragment("Hello?Flutter"));
????????ft.commit();
????}
}
flutter?build?apk?--debug
打包構(gòu)建的 flutter-debug.aar 位于 .android/Flutter/build/outputs/aar/ 目錄下,可以把它拷貝到原生 Android 工程的 app/libs 目錄下,然后在原生 Android 工程的 app 目錄的打包配置 build.gradle 中添加對它的依賴,如下所示。
dependencies?{
??implementation(name:?'flutter-debug',?ext:?'aar')???
}
iOS 集成 Flutter
在原生 iOS 工程中集成 Flutter 需要先配置好 CocoaPods,CocoaPods 是 iOS 的類庫管理工具,用來管理第三方開源庫。在原生 iOS 工程中執(zhí)行 pod init 命令創(chuàng)建一個 Podfile 文件,然后在 Podfile 文件中添加 Flutter 模塊依賴,如下所示。
flutter_application_path?=?'../flutter_?library/
load?File.join(flutter_application_path,?'.ios',?'Flutter',?'podhelper.rb')
target?'iOSDemo'?do
??#?Comment?the?next?line?if?you?don't?want?to?use?dynamic?frameworks
??use_frameworks!
??install_all_flutter_pods(flutter_application_path)
??#?Pods?for?iOSDemo
??…?//省略其他腳本
end?'
默認情況下,F(xiàn)lutter 是不支持 Bitcode 的,Bitcode 是一種 iOS 編譯程序的中間代碼,在原生 iOS 工程中集成 Flutter 需要禁用 Bitcode。在 Xcode 中依次選擇【TAGETS】→【Build Setttings】→【Build Options】→【Enable Bitcode】來禁用 Bitcode,如下圖所示。

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh"?build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh"?embed不過,最新版本的Flutter已經(jīng)不需要再添加腳本了。重新運行原生iOS工程,如果沒有任何錯誤則說明iOS成功集成Flutter模塊。
除了使用Flutter模塊方式外,還可以將Flutter模塊打包成可以依賴的動態(tài)庫,然后再使用CocoaPods添加動態(tài)庫。首先,在flutter_library根目錄下執(zhí)行打包構(gòu)建命令生成framework動態(tài)庫,如下所示。flutter?build?ios?--debug
然后,在原生 iOS 工程的根目錄下創(chuàng)建一個名為 FlutterEngine 的目錄,并把生成的兩個framework 動態(tài)庫文件拷貝進去。不過,iOS 生成模塊化產(chǎn)物要比 Android 多一個步驟,因為需要把 Flutter 工程編譯生成的庫手動封裝成一個 pod。首先,在 flutter_ library 該目錄下創(chuàng)建 FlutterEngine.podspec,然后添加如下腳本代碼。
Pod::Spec.new?do?|s|
??s.name?????????????=?'FlutterEngine'
??s.version??????????=?'0.1.0'
??s.summary??????????=?'FlutterEngine'
??s.description??????=?<<-DESC
TODO:?Add?long?description?of?the?pod?here.
???????????????????????DESC
??s.homepage?????????=?'https://github.com/xx/FlutterEngine'
??s.license??????????=?{?:type?=>?'MIT',?:file?=>?'LICENSE'?}
??s.author???????????=?{?'xzh'?=>?'[email protected]'?}
??s.source???????=?{?:git?=>?"",?:tag?=>?"#{s.version}"?}
??s.ios.deployment_target?=?'9.0'
??s.ios.vendored_frameworks?=?'App.framework',?'Flutter.framework'
end
target?'iOSDemo'?do
????pod?'FlutterEngine',?:path?=>?'./'
end
#import?"ViewController.h"
#import?
#import?
@interface?ViewController?()
@end
@implementation?ViewController
-?(void)viewDidLoad?{
????[super?viewDidLoad];
????UIButton?*button?=?[[UIButton?alloc]init];
????[button?setTitle:@"加載Flutter模塊"?forState:UIControlStateNormal];
????button.backgroundColor=[UIColor?redColor];
????button.frame?=?CGRectMake(50,?50,?200,?100);
????[button?setTitleColor:[UIColor?redColor]?forState:UIControlStateHighlighted];
????[button?addTarget:self?action:@selector(buttonPrint)?forControlEvents:UIControlEventTouchUpInside];
????[self.view?addSubview:button];
}
-?(void)buttonPrint{
????FlutterViewController?*?flutterVC?=?[[FlutterViewController?alloc]init];
????[flutterVC?setInitialRoute:@"defaultRoute"];
????[self?presentViewController:flutterVC?animated:true?completion:nil];
}
@end

Flutter 模塊調(diào)試
那么,能不能在混合項目中開啟 Flutter 的熱重載呢?答案是可以的,只需要經(jīng)過如下步驟即可開啟熱重載功能。首先,關閉原生應用,此處所說的關閉是指關閉應用的進程,而不是簡單的退出應用。在 Flutter 模塊的根目錄中輸入 flutter attach 命令,然后再次打開原生應用,就會看到連接成功的提示,如下圖所示。

在 Flutter 工程中,我們可以直接點擊debug按鈕來進行代碼調(diào)試,但在混合項目中,直接點擊 debug 按鈕是不起作用的。此時,可以使用 Android Studio 提供的 flutter attach 按鈕來建立與 flutter 模塊的連接,進行實現(xiàn)對 flutter 模塊的代碼調(diào)試,如圖下圖所示。


