技术干货 | Flutter 混合开发基础
?
?
導(dǎo)讀:Flutter 支持以獨(dú)立頁面、甚至是 UI 片段的方式,集成到現(xiàn)有的應(yīng)用中,即所謂的混合開發(fā)模式。本文主要談?wù)?Android 平臺(tái)下, Flutter 的混合開發(fā)與構(gòu)建。
?
文|李成達(dá) 網(wǎng)易云信資深移動(dòng)端開發(fā)工程師
Flutter 作為 Google 開源的新一代跨平臺(tái)、高性能 UI 框架,旨在幫助開發(fā)者高效地構(gòu)建出跨平臺(tái)的、UI 與交互體驗(yàn)一致的精美應(yīng)用,推出后一直倍受開發(fā)者的青睞。
當(dāng)需要開發(fā)一個(gè)全新的應(yīng)用時(shí),我們可以很方便地從零開始,完全使用 Flutter 進(jìn)行開發(fā)。但如果是針對一個(gè)現(xiàn)有的應(yīng)用,需要引入 Flutter 技術(shù),顯然使用 Flutter 全部重寫一遍是不現(xiàn)實(shí)的。幸運(yùn)的是,Flutter 很好地支持了以獨(dú)立頁面、甚至是 UI 片段的方式集成到現(xiàn)有的應(yīng)用中,即所謂的混合開發(fā)模式。本文主要從一個(gè) Android 開發(fā)的視角,談?wù)?Android 平臺(tái)下, Flutter 的混合開發(fā)與構(gòu)建。
?
Hello Flutter
相信現(xiàn)在應(yīng)該很少會(huì)有移動(dòng)端開發(fā)者不知道 Flutter,這里不再做過多介紹。對于這門技術(shù),使用過的應(yīng)該絕大多數(shù)都會(huì)說好;沒用過的推薦嘗試一下,跑個(gè) Demo 體驗(yàn)體驗(yàn),有可能它就是你需要學(xué)習(xí)和掌握的最后一門新技術(shù)了。回過頭來,Flutter 究竟有什么獨(dú)特的魅力讓它能從一眾技術(shù)中脫穎而出呢?總結(jié)一下,主要有以下幾點(diǎn):
-
跨平臺(tái):可以做到一套代碼完美適配 Android、iOS 平臺(tái),未來還會(huì)覆蓋更多平臺(tái),大大節(jié)省了開發(fā)人力與維護(hù)成本,同時(shí)擁有出色的跨端 UI 表現(xiàn)一致性。
-
高效開發(fā):SDK 提供了豐富的 UI 組件,開箱即用;聲明式的 UI 構(gòu)建方式,大大減少出錯(cuò)率;Debug 模式提供熱重載能力,可實(shí)時(shí)預(yù)覽代碼變更,不需要重新編譯安裝。
-
高性能:采用自建渲染引擎,獨(dú)立于系統(tǒng)并可單獨(dú)優(yōu)化;區(qū)別于 RN、WEEX,沒有中間層轉(zhuǎn)換的額外開銷;Release 模式下代碼編譯為 AOT 指令,運(yùn)行高效。
受益于以上的核心優(yōu)勢,Flutter 推出后圈了很多移動(dòng)開發(fā)者的粉,各互聯(lián)網(wǎng)大廠也紛紛將其作為一項(xiàng)基礎(chǔ)技術(shù)進(jìn)行研究。在 Flutter 初期,其應(yīng)用場景主要是從 0 構(gòu)建一個(gè)全新 App,對混合開發(fā)的支持很不友好。但作為一門跨平臺(tái)的技術(shù)框架,到底還是需要依賴原生平臺(tái)提供的諸多系統(tǒng)能力,此外還有眾多現(xiàn)存原生 App 躍躍欲試,因此在這個(gè)需求背景下,混合開發(fā)的支持與完善至今已發(fā)展得越來越好,下面我們就用一個(gè)簡單的示例開始 Android 端的 Flutter 混合開發(fā)與構(gòu)建之旅。
?
引入 Flutter 模塊
要在一個(gè)已有的 Android Project 中使用 Flutter,需要引入一個(gè) Flutter Module。在 Android Studio(需要確保 Flutter 插件已經(jīng)成功安裝并啟用)中打開現(xiàn)有 Android 工程,通過使用 File > New > New Module… 菜單,我們可以新創(chuàng)建一個(gè) Flutter 模塊或是導(dǎo)入一個(gè)外部的 Flutter 模塊。
這里以最簡單的 Android App 項(xiàng)目為例,導(dǎo)入 Flutter 模塊。在 Flutter 模塊導(dǎo)入成功之后,原工程文件、結(jié)構(gòu)都會(huì)發(fā)生一些變化,主要有:
-
settings.gradle 文件新增了以下內(nèi)容。其實(shí)就是執(zhí)行對應(yīng) Flutter 模塊下 .android/include_flutter.groovy 腳本文件,該步驟會(huì)引入一個(gè)名為 Flutter 的 Android Library Module,同時(shí)還會(huì)引入 Flutter 模塊所依賴的所有插件。
-
項(xiàng)目結(jié)構(gòu)變化,如下圖所示:
在引入 Flutter 模塊之前,項(xiàng)目中僅有 app 一個(gè) Module;而在引入之后,可以看到除了原有的 app Module 外,Flutter Gradle 插件自動(dòng)引入了額外幾個(gè)子 Module:
-
flutter_module:指代要引入的目標(biāo) Flutter Module,不會(huì) apply Android 相關(guān)的任何插件,主要是包含 Flutter 相關(guān)源碼、資源、依賴等。
-
flutter:為 Flutter Gradle 插件引入的 Android Library Module;主要負(fù)責(zé)編譯 flutter_module 及其依賴的第三方 Package、Plugin 的 Dart 代碼,以及打包 Flutter 資源等。
-
device_info:為 Flutter Gradle 插件自動(dòng)引入的 Flutter Android Plugin Library Module,這是因?yàn)橐婚_始我在 flutter_module 的 pubspec.yaml 文件中添加了對 device_info 這個(gè)插件的依賴。Flutter Gradle 工具會(huì)將 flutter_module 依賴到的所有插件其 Android 平臺(tái)側(cè)的代碼、資源作為一個(gè) Library Module 引入到項(xiàng)目中一起參與構(gòu)建。如果要查看 flutter_module 引入了哪些 Plugin,可以查看其對應(yīng)目錄下的 .flutter-plugins 與 .flutter-plugins-dependencies 文件,這兩個(gè)文件是執(zhí)行 flutter pub get 時(shí)生成的,記錄了插件的本地文件目錄、依賴信息等。
注意:一個(gè)工程不能包含多個(gè) Flutter Module,最多只能引入一個(gè),這是由 Flutter 的 Gradle 插件決定的。
?
使用 Flutter
完成 Flutter 模塊的引入后,我們再來看看如何使用 Flutter。
?添加依賴?
首先需要在 App 模塊的build.gradle腳本文件中添加對Flutter工程的依賴,只有這樣 Flutter 模塊才會(huì)參與到整個(gè)應(yīng)用的構(gòu)建中來,我們也才能夠在 App 模塊中調(diào)用到 Flutter 提供的 Java 層 API。如下所示:
dependencies { implementation project(':flutter') }?運(yùn)行 Flutter 頁面?
我們可以選擇使用 Activity、Fragment 或者 View 來承載 Flutter 的 UI,這里主要介紹前面兩種方式,并假設(shè)flutter_module中已經(jīng)通過runApp方法渲染了一個(gè)widget。
-
運(yùn)行 Flutter Activity。使用io.flutter.embedding.android.FlutterActivity類可以很方便的啟動(dòng)一個(gè) Flutter Activity,當(dāng)然我們也可以繼承它并擴(kuò)展自己的邏輯。示例代碼如下:
-
運(yùn)行 Flutter Fragment。可以使用FlutterFragmentActivity或者FlutterFragment來添加 Flutter UI 片段:a. 使用FlutterFragmentActivity可以自動(dòng)創(chuàng)建并添加一個(gè)FlutterFragment;b. 手動(dòng)創(chuàng)建FlutterFragment后添加到目標(biāo) Activity 中。示例代碼如下:
-
平臺(tái)層和 Flutter 層通信。不論是開發(fā) Plugin 還是業(yè)務(wù)邏輯,平臺(tái)層與 Flutter 層通信是必不可少的,為此就需要使用到MethodChannel。平臺(tái)層通過MethodChannel請求調(diào)用 Flutter 層 API 時(shí),數(shù)據(jù)在經(jīng)過打包編碼后,通過 JNI、DartVM 傳到 Flutter 層解碼后使用;待結(jié)果計(jì)算完成后,又會(huì)重新打包編碼,經(jīng)過 DartVM、JNI 傳回到 Native 層;同理,在 Flutter 層請求調(diào)用平臺(tái)層的 API 時(shí),數(shù)據(jù)處理是一致的,只是流轉(zhuǎn)方向相反。通過這種方式,平臺(tái)層與 Flutter 層就建立了一個(gè)雙向的、異步的通信通道。在下面的示例代碼中,Native 層使用dev.flutter.example/counter創(chuàng)建一個(gè)MethodChannel,并設(shè)置 Handler 接收 Dart 的遠(yuǎn)程方法調(diào)用 incrementCounter,并調(diào)用 reportCounter 將結(jié)果回傳。
Dart 層使用相同的名稱創(chuàng)建 MethodChannel,并設(shè)置 Handler 處理回調(diào)結(jié)果,隨后調(diào)用 incrementCounter 方法請求 counter。示例代碼如下:
final _channel = MethodChannel('dev.flutter.example/counter'); _channel.setMethodCallHandler(_handleMessage); _channel.invokeMethod('incrementCounter'); Future<dynamic> _handleMessage(MethodCall call) async { if (call.method == 'reportCounter') { _count = call.arguments as int; notifyListeners(); } }這里我們是通過手動(dòng)創(chuàng)建 MethodChannel 進(jìn)行通信的,這在進(jìn)行簡單通信的場景是沒問題的,但在通信接口 API 比較復(fù)雜的情況就不是很適用了。
一是繁瑣,因?yàn)槲覀冃枰謱懘罅康拇虬⒉鸢a;二是容易出錯(cuò)。這個(gè)時(shí)候就輪到 ?Pigeon 大顯身手了。Pigeon 是一個(gè)官方推出的代碼生成工具,可以生成類型安全的雙向通信 API 接口,具體可以參考官方的 Example,這里不再贅述。
Pigeon :https://flutter.dev/docs/development/platform-integration/platform-channels#pigeon
?
Flutter APK 解析
到這里,我們已經(jīng)了解了如何在現(xiàn)有 Android 項(xiàng)目中引入并使用 Flutter,接下來我們再來探究一下 Flutter APK 的結(jié)構(gòu),看看 Flutter Tools 在這個(gè) APK 包內(nèi)到底打包了哪些東西。下面兩圖分別為 Debub 模式和 Release 模式下構(gòu)建出來的 Flutter APK 包結(jié)構(gòu),忽略了非 Flutter 相關(guān)的項(xiàng)。
可以看到兩個(gè)模式下的 APK 結(jié)構(gòu)大致相同,說明如下:
-
lib/{arch}/libflutter.so:為對應(yīng)架構(gòu)的 Flutter Engine 共享庫,負(fù)責(zé) Flutter 渲染、JNI 通信、DartVM。如果不需要對應(yīng)架構(gòu)的版本,通過 abiFilters 可以 Exclude 掉。
-
lib/{arch}/libapp.so:只存在于 Release 模式下,共享庫中包含 Dart AOT 生成的二進(jìn)制指令和數(shù)據(jù)。在運(yùn)行時(shí),Flutter Engine 通過 Dynamic Load 的方式,從共享庫中讀取對應(yīng)的可執(zhí)行機(jī)器指令以及數(shù)據(jù)。
-
assets/flutter_assets:Flutter 引用到的相關(guān)資源:
-
fonts:包含字體庫。
-
FontManifest.json:引用到的字體庫清單文件,json 格式,所有使用到的字體、以及字體文件在 flutter_assets 下的路徑。
-
AssetManifest.json:其他資源清單文件,json 格式,為所有資源名稱到資源路徑的映射,Flutter 在加載某一項(xiàng)資源時(shí),會(huì)通過這個(gè)配置清單找到對應(yīng)路徑的資源進(jìn)行讀取后加載。
-
kernel_blob.bin、isolate_snapshot_data、vm_snapshot_data:只存在于 Debug 模式下,分別為 DartVM 字節(jié)碼與數(shù)據(jù),其作用類似于 libapp.so,只是存在形式、打包方式不同。在 Debug 模式下,Flutter Tools 將指令和數(shù)據(jù)分別打包,主要是為了熱重載(HotReload)服務(wù)的,而在 Release 模式下是統(tǒng)一打包成共享庫。
-
?
踩過的坑
這里,也總結(jié)了幾個(gè)我們在應(yīng)用的時(shí)候遇到的問題,供大家參考避坑。
-
路由管理復(fù)雜:這里面包括 Flutter 層內(nèi)部的頁面路由管理以及 Flutter 與原生的混合棧管理。前者在 Navigator 2.0 API 中已經(jīng)得到了很好的完善與支持,但后者仍面臨著諸多限制與不足,需要改進(jìn)。目前項(xiàng)目中還未涉及到后者這種很復(fù)雜的業(yè)務(wù)場景,因此對這一塊的研究比較少,感興趣的同學(xué)可以了解一下諸如 flutter_boost 此類的開源解決方案。
-
生命周期不對應(yīng):Android 的組件一般都會(huì)有自己的生命周期,Flutter 的 Widget State 也有一套自己的生命周期,但這兩者其實(shí)并不是一一對應(yīng)的。比如原生的 Activity 頁面雖然已經(jīng)被 Finish 并 Destroy 掉了,但 Flutter 層的頁面并不一定會(huì)隨之而被 Dispose,尤其是在使用 Cache Flutter Engine 的時(shí)候。Flutter 頁面是可以脫離原生頁面而存在的,它們可以被動(dòng)態(tài)地 Attach 和 Detach,Attach 時(shí)會(huì)觸發(fā)重新渲染,Detach 時(shí) UI 相關(guān)的所有操作都會(huì) Pending 直到重新被 Attach。所以在混合開發(fā)中,業(yè)務(wù)邏輯不應(yīng)該過度依賴 Widget State 的一些生命周期方法,因?yàn)樗鼈兛赡軙?huì)被延后執(zhí)行從而導(dǎo)致一些奇怪的 Bug。
?
總結(jié)
Flutter 混合開發(fā)使得開發(fā)者可以漸進(jìn)式地進(jìn)行 Flutter 開發(fā)與遷移,是 Flutter 寄生于原生平臺(tái)至關(guān)重要的一環(huán)。
本文主要從一個(gè) Android 開發(fā)的視角,介紹了 Flutter 混合開發(fā)的入門知識(shí)。隨著 Flutter 開源項(xiàng)目的不斷迭代與演進(jìn),混合開發(fā)的體驗(yàn)正在變得越來越好、性能也越來越高。但美中不足的是仍然有一些應(yīng)用場景與問題并未得到很好地完善與解決,比如 Flutter 多實(shí)例問題(我們也將在下個(gè)月的另一篇文章中跟大家分享介紹我們在實(shí)踐 Flutter 多實(shí)例遇到的問題與解決方案,敬請關(guān)注)。
瑕不掩瑜,Flutter 這門技術(shù)整體而言還是非常不錯(cuò)的,它如今仍處于快速發(fā)展的階段,相信在 Flutter 團(tuán)隊(duì)與開源社區(qū)的共同努力下,未來的生態(tài)值得期待。
?作者介紹?
李成達(dá),網(wǎng)易云信資深移動(dòng)端開發(fā)工程師,熱衷于研究跨平臺(tái)開發(fā)技術(shù)以及工程提效,目前主要負(fù)責(zé)視頻會(huì)議組件化 SDK 的相關(guān)研發(fā)工作。
?延伸閱讀?
-
技術(shù)干貨 | 基于 Qt Quick Plugin 快速構(gòu)建桌面端跨平臺(tái)組件
-
技術(shù)實(shí)踐 | Android 設(shè)備音視頻兼容性適配
-
技術(shù)干貨 | iOS 高階容器詳解
?
總結(jié)
以上是生活随笔為你收集整理的技术干货 | Flutter 混合开发基础的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 技术干货 | 基于 Qt Quick P
- 下一篇: 娱乐社交,玩票大的!2021网易云信“融