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

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

可以看到,和普通的Flutter工程相比,F(xiàn)lutter模塊工程也內(nèi)嵌了Android工程和iOS工程,只不過(guò)默認(rèn)情況下,Android工程和iOS工程是隱藏的。因此,對(duì)于Flutter模塊工程來(lái)說(shuō),也可以像普通工程一樣使用 Android Studio進(jìn)行開(kāi)發(fā)和調(diào)試。
同時(shí),相比普通的Flutter工程,F(xiàn)lutter模塊工程的Android工程目錄下多了一個(gè)Flutter目錄,此目錄下的build.gradle配置就是我們構(gòu)建aar時(shí)的打包配置。同樣,在Flutter模塊工程的iOS工程目錄下也會(huì)找到一個(gè)Flutter目錄,這也是Flutter模塊工程既能像Flutter普通工程一樣使用Android Studio進(jìn)行開(kāi)發(fā)調(diào)試,又能打包構(gòu)建aar或pod的原因。
Android集成Flutter
在原生Android工程中集成Flutter,原生工程對(duì)Flutter的依賴主要包括兩部分,分別是Flutter庫(kù)和引擎,以及Flutter工程構(gòu)建產(chǎn)物。
- Flutter庫(kù)和引擎:包含icudtl.dat、libFlutter.so以及一些class文件,最終這些文件都會(huì)被封裝到Flutter.jar中。
- Flutter工程產(chǎn)物:包括應(yīng)用程序數(shù)據(jù)段 isolate_snapshot_data、應(yīng)用程序指令段 isolate_snapshot_instr、虛擬機(jī)數(shù)據(jù)段vm_snapshot_data、虛擬機(jī)指令段vm_snapshot_instr以及資源文件flutter_assets。
和原生Android工程集成其他插件庫(kù)的方式一樣,在原生Android工程中引入Flutter模塊需要先在settings.gradle中添加如下代碼。
setBinding(new Binding([gradle: this])) evaluate(new File( settingsDir.parentFile, 'flutter_library/.android/include_flutter.groovy'))
其中,flutter_library為我們創(chuàng)建的Flutter模塊。然后,在原生Android工程的app目錄的build.gradle文件中添加如下依賴。
dependencies {
implementation project(":flutter")
}
然后編譯并運(yùn)行原生Android工程,如果沒(méi)有任何錯(cuò)誤則說(shuō)明集成Flutter模塊成功。需要說(shuō)明的是,由于Flutter支持的最低版本為16,所以需要將Android項(xiàng)目的minSdkVersion修改為16。
如果出現(xiàn)“程序包android.support.annotation不存在”的錯(cuò)誤,需要使用如下的命令來(lái)創(chuàng)建Flutter模塊,因?yàn)樽钚掳姹镜腁ndroid默認(rèn)使用androidx來(lái)管理包。
flutter create --androidx -t module flutter_library
對(duì)于Android原生工程,如果還沒(méi)有升級(jí)到androidx,可以在原生Android工程上右鍵,然后依次選擇【Refactor】→【Migrate to Androidx】將Android工程升級(jí)到androidx包管理。
在原生Android工程中成功添加Flutter模塊依賴后,打開(kāi)原生Android工程,并在應(yīng)用的入口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);
}
}
通過(guò)Flutter提供的createView()方法,可以將Flutter頁(yè)面構(gòu)建成Android能夠識(shí)別的視圖,然后將這個(gè)視圖使用Android提供的addContentView()方法添加到父窗口即可。重新運(yùn)行原生Android工程,最終效果如下圖所示。

如果原生Android的MainActivity加載的是一個(gè)FrameLayout,那么加載只需要將Flutter頁(yè)面構(gòu)建成一個(gè)Fragment即可,如下所示。
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模塊方式集成外,還可以將Flutter模塊打包成aar,然后再添加依賴。在flutter_library根目錄下執(zhí)行aar打包構(gòu)建命令即可抽取Flutter依賴,如下所示。
flutter build apk --debug
此命令的作用是將Flutter庫(kù)和引擎以及工程產(chǎn)物編譯成一個(gè)aar包,上面命令編譯的aar包是debug版本,如果需要構(gòu)建release版本,只需要把命令中的debug換成release即可。
打包構(gòu)建的flutter-debug.aar位于.android/Flutter/build/outputs/aar/目錄下,可以把它拷貝到原生Android工程的app/libs目錄下,然后在原生Android工程的app目錄的打包配置build.gradle中添加對(duì)它的依賴,如下所示。
dependencies {
implementation(name: 'flutter-debug', ext: 'aar')
}
然后重新編譯一下項(xiàng)目,如果沒(méi)有任何錯(cuò)誤提示則說(shuō)明Flutter模塊被成功集成到Android原生工程中。
iOS集成Flutter
原生iOS工程對(duì)Flutter的依賴包含F(xiàn)lutter庫(kù)和引擎,以及Flutter工程編譯產(chǎn)物。其中,F(xiàn)lutter 庫(kù)和引擎指的是Flutter.framework等,F(xiàn)lutter工程編譯產(chǎn)物指的是 App.framework等。
在原生iOS工程中集成Flutter需要先配置好CocoaPods,CocoaPods是iOS的類庫(kù)管理工具,用來(lái)管理第三方開(kāi)源庫(kù)。在原生iOS工程中執(zhí)行pod init命令創(chuàng)建一個(gè)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 '
然后,關(guān)閉原生iOS工程,并在原生iOS工程的根目錄執(zhí)行pod install命令安裝所需的依賴包。安裝完成后,使用Xcode打開(kāi)iOSDemo.xcworkspace原生工程。
默認(rèn)情況下,F(xiàn)lutter是不支持Bitcode的,Bitcode是一種iOS編譯程序的中間代碼,在原生iOS工程中集成Flutter需要禁用Bitcode。在Xcode中依次選擇【TAGETS】→【Build Setttings】→【Build Options】→【Enable Bitcode】來(lái)禁用Bitcode,如下圖所示。

如果使用的是Flutter早期的版本,還需要添加build phase來(lái)支持構(gòu)建Dart代碼。依次選擇【TAGGETS】→【Build Settings】→【Enable Phases】,然后點(diǎn)擊左上角的加號(hào)新建一個(gè)“New Run Script Phase”,添加如下腳本代碼。
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
不過(guò),最新版本的Flutter已經(jīng)不需要再添加腳本了。重新運(yùn)行原生iOS工程,如果沒(méi)有任何錯(cuò)誤則說(shuō)明iOS成功集成Flutter模塊。
除了使用Flutter模塊方式外,還可以將Flutter模塊打包成可以依賴的動(dòng)態(tài)庫(kù),然后再使用CocoaPods添加動(dòng)態(tài)庫(kù)。首先,在flutter_library根目錄下執(zhí)行打包構(gòu)建命令生成framework動(dòng)態(tài)庫(kù),如下所示。
flutter build ios --debug
上面命令是將Flutter工程編譯成Flutter.framework和App.framework動(dòng)態(tài)庫(kù)。如果要生成release版本,只需要把命令中的debug換成release即可。
然后,在原生iOS工程的根目錄下創(chuàng)建一個(gè)名為FlutterEngine的目錄,并把生成的兩個(gè)framework動(dòng)態(tài)庫(kù)文件拷貝進(jìn)去。不過(guò),iOS生成模塊化產(chǎn)物要比Android多一個(gè)步驟,因?yàn)樾枰袴lutter工程編譯生成的庫(kù)手動(dòng)封裝成一個(gè)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' => '1044817967@qq.com' }
s.source = { :git => "", :tag => "#{s.version}" }
s.ios.deployment_target = '9.0'
s.ios.vendored_frameworks = 'App.framework', 'Flutter.framework'
end
然后,執(zhí)行pod lib lint命令即可拉取Flutter模塊所需的組件。接下來(lái),在原生iOS工程的Podfile文件添加生成的庫(kù)即可。
target 'iOSDemo' do pod 'FlutterEngine', :path => './' end
重新執(zhí)行pod install命令安裝依賴庫(kù),原生iOS工程集成Flutter模塊就完成了。接下來(lái),使用Xcode打開(kāi)ViewController.m文件,然后添加如下代碼。
#import "ViewController.h"
#import <Flutter/Flutter.h>
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
@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
在上面的代碼中,我們?cè)谠鷌OS中創(chuàng)建了一個(gè)按鈕,點(diǎn)擊按鈕時(shí)就會(huì)跳轉(zhuǎn)到Flutter頁(yè)面,最終效果如下圖所示。

默認(rèn)情況下,F(xiàn)lutter為提供了兩種調(diào)用方式,分別是FlutterViewController和FlutterEngine。對(duì)于FlutterViewController來(lái)說(shuō),打開(kāi)ViewController.m文件,在里面添加一個(gè)加載flutter頁(yè)面的方法并且添加一個(gè)按鈕看來(lái)調(diào)用。
Flutter模塊調(diào)試
眾所周知,F(xiàn)lutter的優(yōu)勢(shì)之一就是在開(kāi)發(fā)過(guò)程中使用熱重載功能來(lái)實(shí)現(xiàn)快速調(diào)試。默認(rèn)情況下,在原生工程中集成Flutter模塊后熱重載功能是失效的,需要重新運(yùn)行原生工程才能看到效果。如此一來(lái),F(xiàn)lutter開(kāi)發(fā)的熱重載優(yōu)勢(shì)就失去了,并且開(kāi)發(fā)效率也隨之降低。
那么,能不能在混合項(xiàng)目中開(kāi)啟Flutter的熱重載呢?答案是可以的,只需要經(jīng)過(guò)如下步驟即可開(kāi)啟熱重載功能。首先,關(guān)閉原生應(yīng)用,此處所說(shuō)的關(guān)閉是指關(guān)閉應(yīng)用的進(jìn)程,而不是簡(jiǎn)單的退出應(yīng)用。在Flutter模塊的根目錄中輸入flutter attach命令,然后再次打開(kāi)原生應(yīng)用,就會(huì)看到連接成功的提示,如下圖所示。

如果同時(shí)連接了多臺(tái)設(shè)備,可以使用flutter attach -d 命令來(lái)指定連接的設(shè)備。接下來(lái),只需要按r鍵即可執(zhí)行熱重載,按R鍵即可執(zhí)行熱重啟,按d鍵即可斷開(kāi)連接。
在Flutter工程中,我們可以直接點(diǎn)擊debug按鈕來(lái)進(jìn)行代碼調(diào)試,但在混合項(xiàng)目中,直接點(diǎn)擊debug按鈕是不起作用的。此時(shí),可以使用Android Studio提供的flutter attach按鈕來(lái)建立與flutter模塊的連接,進(jìn)行實(shí)現(xiàn)對(duì)flutter模塊的代碼調(diào)試,如圖下圖所示。

上面只是完成了在原生工程中引入Flutter模塊,具體開(kāi)發(fā)時(shí)還會(huì)遇到與Flutter模塊的通信問(wèn)題、路由管理問(wèn)題,以及打包等。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android利用Intent.ACTION_SEND進(jìn)行分享
這篇文章主要介紹了Android利用Intent.ACTION_SEND進(jìn)行分享,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
Android使用OkHttp進(jìn)行重定向攔截處理的方法
這篇文章主要介紹了Android使用OkHttp進(jìn)行重定向攔截處理的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
Android跨進(jìn)程拋異常的原理的實(shí)現(xiàn)
這篇文章主要介紹了Android跨進(jìn)程拋異常的原理的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11
Flutter實(shí)現(xiàn)頂部導(dǎo)航欄功能
這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)頂部導(dǎo)航欄功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
android 獲取手機(jī)內(nèi)存及 內(nèi)存可用空間的方法
下面小編就為大家?guī)?lái)一篇android 獲取手機(jī)內(nèi)存及SD卡內(nèi)存可用空間的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03
android實(shí)現(xiàn)圖片上傳功能(springMvc)
這篇文章主要為大家詳細(xì)介紹了android結(jié)合springMvc實(shí)現(xiàn)圖片上傳的相關(guān)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Android手勢(shì)操作示例(上/下/左/右的判斷)
這篇文章主要介紹了Android手勢(shì)操作方法,包含了針對(duì)上、下、左、右等方向的判斷,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06

