使用 Flutter 從頭開始寫一個 App是一件輕鬆愜意的事情。可是對於成熟產品來講,徹底摒棄原有 App 的歷史沉澱,全面轉向 Flutter 並不現實。用 Flutter 去統一 iOS/Android 技術棧,把它做爲已有原生 App 的擴展,而後經過逐步試驗有序推動從而提高終端開發效率,可能纔是現階段 Flutter 最有效的集成方式。java
那麼,Flutter 工程與原生工程該如何組織管理?不一樣平臺的 Flutter 工程打包構建產物該如何抽取封裝?封裝後的產物該如何引入原生工程?原生工程又該如何使用封裝後的 Flutter 能力?android
這些問題使得在已有原生 App 中接入 Flutter 看似並非一件容易的事情。那接下來,我就和你介紹下如何在原生 App 中以最天然的方式接入 Flutter。ios
既然要在原生應用中混編 Flutter,相信你必定已經準備好了原生應用工程。若是你尚未準備好也不要緊,我會以一個最小化的示例和你演示這個改造過程。git
首先,咱們分別用 Xcode 與 Android Studio 快速創建一個只有首頁的基本工程,工程名分別爲 iOSDemo 與 AndroidDemo。github
到此,Android 工程就已經準備好了;而對於 iOS 工程來講,因爲基本工程並不支持以組件化的方式管理項目,所以咱們還須要多作一步,將其改形成使用 CocoaPods 管理的工程,也就是要在 iOSDemo 根目錄下建立一個只有基本信息的 Podfile 文件。Podfile文件的配置以下:bash
use_frameworks!
platform :ios, '8.0'
target 'iOSDemo' do
#todo
end
複製代碼
而後,在命令行輸入 pod install 命令後,會自動生成一個 iOSDemo.xcworkspace 文件,該文件存放的就是咱們項目須要的依賴庫,這時咱們就完成了 iOS 工程改造。app
若是你想要在已有的原生 App 裏嵌入一些 Flutter 頁面,有兩個辦法能夠實現,即統一管理模式和三端分離模式。框架
因爲 Flutter 早期提供的混編方式能力及相關資料有限,國內較早使用 Flutter 混合開發的團隊大多使用的是統一管理模式。可是,隨着功能迭代的深刻,這種方案的弊端也隨之顯露,不只三端(Android、iOS、Flutter)代碼耦合嚴重,相關工具鏈耗時也隨之大幅增加,致使開發效率下降。模塊化
因此,後續使用 Flutter 混合開發的團隊陸續按照三端代碼分離的模式來進行依賴治理,實現了 Flutter 工程的輕量級接入。工具
除此以外,三端代碼分離模式還能夠把 Flutter 模塊做爲原生工程的子模塊,從而快速實現 Flutter 功能的「熱插拔」,下降原生工程改造的成本。而 Flutter 工程經過 Android Studio 進行管理,無需打開原生工程,可直接進行 Dart 代碼和原生代碼的開發調試。
三端工程分離模式的關鍵是抽離 Flutter 工程,將不一樣平臺的構建產物依照標準組件化的形式進行管理,即 Android 使用 aar、iOS 使用 pod。換句話說,接下來介紹的混編方案會將 Flutter 模塊打包成 aar 和 pod,這樣原生工程就能夠像引用其餘第三方原生組件庫那樣快速接入 Flutter 了。
當咱們建立一個新的Flutter 工程時,除了一些通用配置外,Flutter還包括 Flutter 工程和原生工程的目錄(即 iOS 和 Android 兩個目錄)。在這種狀況下,原生工程就會依賴於 Flutter 相關的庫和資源,從而沒法脫離父目錄進行獨立構建和運行。
原生工程對 Flutter 的依賴主要分爲兩部分:
在已經有原生工程的狀況下,咱們須要在同級目錄建立 Flutter 模塊,構建 iOS 和 Android 各自的 Flutter 依賴庫。這也很好實現,Flutter 就爲咱們提供了這樣的命令。咱們只須要在原生項目的同級目錄下,執行 Flutter 命令建立名爲 flutter_library 的模塊便可,命令以下。
Flutter create -t module flutter_library
複製代碼
這裏的 Flutter 模塊,也是 Flutter 工程,咱們用 Android Studio 打開它,其目錄以下圖所示。
仔細查看能夠發現,Flutter 模塊有一個細微的變化:Android 工程下多了一個 Flutter 目錄,這個目錄下的 build.gradle 配置就是咱們構建 aar 的打包配置。這就是模塊工程既能像 Flutter 傳統工程同樣使用 Android Studio 開發調試,又能打包構建 aar 與 pod 的祕密。
實際上,iOS 工程的目錄結構也有細微變化,但這個差別並不影響打包構建,所以此處就再也不展開了。
而後,咱們打開 main.dart 文件,將其邏輯更新爲如下代碼邏輯,即一個寫着「Hello from Flutter」的全屏紅色的 Flutter Widget,以下所示。
import 'package:flutter/material.dart';
import 'dart:ui';
void main() => runApp(_widgetForRoute(window.defaultRouteName));//獨立運行傳入默認路由
Widget _widgetForRoute(String route) {
switch (route) {
default:
return MaterialApp(
home: Scaffold(
backgroundColor: const Color(0xFFD63031),//ARGB紅色
body: Center(
child: Text(
'Hello from Flutter', //顯示的文字
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 20.0,
color: Colors.blue,
),
),
),
),
);
}
}
複製代碼
咱們建立的 Widget 其實是包在一個 switch-case 語句中的。這是由於封裝的 Flutter 模塊通常會有多個頁面級 Widget,原生 App 代碼則會經過傳入路由標識字符串,告訴 Flutter 究竟應該返回何種 Widget。爲了簡化案例,在這裏咱們忽略標識字符串,統一返回一個 MaterialApp。
接下來,咱們要作的事情就是把這段代碼編譯打包,構建出對應的 Android 和 iOS 依賴庫,實現原生工程的接入。
如今,咱們首先來看看 Android 工程如何接入。
以前咱們提到原生工程對 Flutter 的依賴主要分爲兩部分,對應到 Android 平臺,這兩部分分別是:
搞清楚 Flutter 工程的 Android 編譯產物以後,咱們須要對 Android 的 Flutter 依賴進行抽取,步驟以下。
首先,在 Flutter_library 的根目錄下,執行 aar 打包構建命令,以下所示。
Flutter build apk --debug
複製代碼
這條命令的做用是編譯工程產物,並將 Flutter.jar 和工程產物編譯結果封裝成一個 aar,以下圖所示。
打包構建的 flutter-debug.aar 位於.android/Flutter/build/outputs/aar/ 目錄下,咱們把它拷貝到原生 Android 工程 AndroidDemo 的 app/libs 目錄下,並在 App 的打包配置 build.gradle 中添加對它的依賴。
...
repositories {
flatDir {
dirs 'libs' // aar目錄
}
}
android {
...
compileOptions {
sourceCompatibility 1.8 //Java 1.8
targetCompatibility 1.8 //Java 1.8
}
...
}
dependencies {
...
implementation(name: 'flutter-debug', ext: 'aar')//Flutter模塊aar
...
}
複製代碼
Sync 一下項目,Flutter 模塊就被添加到了 Android 項目中。
而後,咱們試着改一下 MainActivity.java 的代碼,把它的 contentView 改爲 Flutter 的 widget,以下所示。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View FlutterView = Flutter.createView(this, getLifecycle(), "defaultRoute"); //傳入路由標識符
setContentView(FlutterView);//用FlutterView替代Activity的ContentView
}
複製代碼
從新運行Android原生工程,效果以下圖。
iOS 工程接入的狀況要稍微複雜一些。在 iOS 平臺,原生工程對 Flutter 的依賴分別是:
iOS 平臺的 Flutter 模塊抽取,實際上就是經過打包命令生成這兩個產物,並將它們封裝成一個 pod 供iOS原生工程引用。
相似地,首先咱們在 Flutter_library 的根目錄下,執行 iOS 打包構建命令。
Flutter build ios --debug
複製代碼
這條命令的做用是編譯 Flutter 工程生成兩個產物:Flutter.framework 和 App.framework。一樣,把 debug 換成 release 就能夠構建 release 產物(固然,你還須要處理一下簽名問題)。
而後,在 iOSDemo 的根目錄下建立一個名爲 FlutterEngine 的目錄,並把這兩個 framework 文件拷貝進去。iOS 的模塊化產物工做要比 Android 多一個步驟,由於咱們須要把這兩個產物手動封裝成 pod。所以,咱們還須要在該目錄下建立 FlutterEngine.podspec,即 Flutter 模塊的組件定義。
Pod::Spec.new do |s|
s.name = 'FlutterEngine'
s.version = '0.1.0'
s.summary = 'XXXXXXX'
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 = { 'chenhang' => 'hangisnice@gmail.com' }
s.source = { :git => "", :tag => "#{s.version}" }
s.ios.deployment_target = '8.0'
s.ios.vendored_frameworks = 'App.framework', 'Flutter.framework'
end
複製代碼
而後,執行pod lib lint 命令,Flutter 模塊組件就已經作好了。接下來,咱們再修改 Podfile 文件把它集成到 iOSDemo 工程中,添加以下腳本。
...
target 'iOSDemo' do
pod 'FlutterEngine', :path => './'
end
複製代碼
而後,執行pod install 命令,Flutter 模塊就集成進 iOS 原生工程中了。再次,咱們試着修改一下 AppDelegate.m 的代碼,把 window 的 rootViewController 改爲 FlutterViewController,以下所示。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
FlutterViewController *vc = [[FlutterViewController alloc]init];
[vc setInitialRoute:@"defaultRoute"]; //路由標識符
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
複製代碼
最後運行原生項目,一個寫着「Hello from Flutter」的全屏紅色的 Flutter Widget 也展現出來了,以下圖所示。
在原生工程中集成Flutter是現階段最多見的方式。經過分離 Android、iOS 和 Flutter 三端工程,抽離 Flutter 庫和引擎及工程代碼爲組件庫,以 Android 和 iOS 平臺最多見的 aar 和 pod 形式接入原生工程,從而將不一樣平臺的構建產物依照標準組件化的形式進行管理。
若是每次經過構建 Flutter 模塊工程,都是手動搬運 Flutter 編譯產物,那很容易就會由於工程管理混亂致使 Flutter 組件庫被覆蓋,從而引起難以排查的 Bug。而要解決此類問題的話,咱們能夠引入 CI 自動構建框架,把 Flutter 編譯產物構建自動化,原生工程經過接入不一樣版本的構建產物,實現更優雅的三端分離模式。