Flutter 混合開發基礎

引言

Flutter 做爲 Google 開源的新一代跨平臺、高性能 UI 框架,旨在幫助開發者高效地構建出跨平臺的、UI 與交互體驗一致的精美應用,推出後一直倍受開發者的青睞。android

當須要開發一個全新的應用時,咱們能夠很方便地從零開始,徹底使用 Flutter 進行開發。但若是是針對一個現有的應用,須要引入 Flutter 技術,顯然使用 Flutter 所有重寫一遍是不現實的。幸運的是,Flutter 很好地支持了以獨立頁面、甚至是 UI 片斷的方式集成到現有的應用中,即所謂的混合開發模式。本文主要從一個 Android 開發的視角,談談 Android 平臺下, Flutter 的混合開發與構建。json

Hello Flutter

相信如今應該不多會有移動端開發者不知道 Flutter,這裏再也不作過多介紹。對於這門技術,使用過的應該絕大多數都會說好;沒用過的推薦嘗試一下,跑個 Demo 體驗體驗,有可能它就是你須要學習和掌握的最後一門新技術了。回過頭來,Flutter 究竟有什麼獨特的魅力讓它能從一衆技術中脫穎而出呢?總結一下,主要有如下幾點:安全

  • 跨平臺:能夠作到一套代碼完美適配 Android、iOS 平臺,將來還會覆蓋更多平臺,大大節省了開發人力與維護成本,同時擁有出色的跨端 UI 表現一致性。
  • 高效開發:SDK 提供了豐富的 UI 組件,開箱即用;聲明式的 UI 構建方式,大大減小出錯率;Debug 模式提供熱重載能力,可實時預覽代碼變動,不須要從新編譯安裝。
  • 高性能:採用自建渲染引擎,獨立於系統並可單獨優化;區別於 RN、WEEX,沒有中間層轉換的額外開銷;Release 模式下代碼編譯爲 AOT 指令,運行高效。

受益於以上的核心優點,Flutter 推出後圈了不少移動開發者的粉,各互聯網大廠也紛紛將其做爲一項基礎技術進行研究。在 Flutter 初期,其應用場景主要是從 0 構建一個全新 App,對混合開發的支持很不友好。但做爲一門跨平臺的技術框架,到底仍是須要依賴原平生臺提供的諸多系統能力,此外還有衆多現存原生 App 躍躍欲試,所以在這個需求背景下,混合開發的支持與完善至今已發展得愈來愈好,下面咱們就用一個簡單的示例開始 Android 端的 Flutter 混合開發與構建之旅。微信

引入 Flutter 模塊

要在一個已有的 Android Project 中使用 Flutter,須要引入一個 Flutter Module。在 Android Studio(須要確保 Flutter 插件已經成功安裝並啓用)中打開現有 Android 工程,經過使用 File > New > New Module… 菜單,咱們能夠新建立一個 Flutter 模塊或是導入一個外部的 Flutter 模塊。架構

引入外部 Flutter 模塊

這裏以最簡單的 Android App 項目爲例,導入 Flutter 模塊。在 Flutter 模塊導入成功以後,原工程文件、結構都會發生一些變化,主要有:app

  • settings.gradle 文件新增瞭如下內容。其實就是執行對應 Flutter 模塊下 .android/include_flutter.groovy 腳本文件,該步驟會引入一個名爲 Flutter 的 Android Library Module,同時還會引入 Flutter 模塊所依賴的全部插件。
setBinding(new Binding([gradle: this]))
evaluate(new File(
    settingsDir.parentFile,
    'flutter_module/.android/include_flutter.groovy'
))
include ':flutter_module'
project(':flutter_module').projectDir = new File('../flutter_module')
  • 項目結構變化,以下圖所示:

項目結構變化

在引入 Flutter 模塊以前,項目中僅有 app 一個 Module;而在引入以後,能夠看到除了原有的 app Module 外,Flutter Gradle 插件自動引入了額外幾個子 Module:框架

  • flutter_module:指代要引入的目標 Flutter Module,不會 apply Android 相關的任何插件,主要是包含 Flutter 相關源碼、資源、依賴等。
  • flutter:爲 Flutter Gradle 插件引入的 Android Library Module;主要負責編譯 flutter_module 及其依賴的第三方 Package、Plugin 的 Dart 代碼,以及打包 Flutter 資源等。
  • device_info:爲 Flutter Gradle 插件自動引入的 Flutter Android Plugin Library Module,這是由於一開始我在 flutter_module 的 pubspec.yaml 文件中添加了對 device_info 這個插件的依賴。Flutter Gradle 工具會將 flutter_module 依賴到的全部插件其 Android 平臺側的代碼、資源做爲一個 Library Module 引入到項目中一塊兒參與構建。若是要查看 flutter_module 引入了哪些 Plugin,能夠查看其對應目錄下的 .flutter-plugins 與 .flutter-plugins-dependencies 文件,這兩個文件是執行 flutter pub get 時生成的,記錄了插件的本地文件目錄、依賴信息等。

注意:一個工程不能包含多個 Flutter Module,最多隻能引入一個,這是由 Flutter 的 Gradle 插件決定的。異步

使用 Flutter

完成 Flutter 模塊的引入後,咱們再來看看如何使用 Flutter。async

添加依賴

首先須要在 App 模塊的build.gradle腳本文件中添加對Flutter工程的依賴,只有這樣 Flutter 模塊纔會參與到整個應用的構建中來,咱們也纔可以在 App 模塊中調用到 Flutter 提供的 Java 層 API。以下所示:工具

dependencies {
  implementation project(':flutter')
}

運行 Flutter 頁面

咱們能夠選擇使用 Activity、Fragment 或者 View 來承載 Flutter 的 UI,這裏主要介紹前面兩種方式,並假設flutter_module中已經經過runApp方法渲染了一個widget。

  • 運行 Flutter Activity。使用io.flutter.embedding.android.FlutterActivity類能夠很方便的啓動一個 Flutter Activity,固然咱們也能夠繼承它並擴展本身的邏輯。示例代碼以下:
FlutterActivity 
  .withNewEngine() 
  .build(context) 
  .also { startActivity(it) }
  • 運行 Flutter Fragment。可使用FlutterFragmentActivity或者FlutterFragment來添加 Flutter UI 片斷:a. 使用FlutterFragmentActivity能夠自動建立並添加一個FlutterFragment;b. 手動建立FlutterFragment後添加到目標 Activity 中。示例代碼以下:
val flutterFragment = FlutterFragment.withNewEngine() 
      .dartEntrypoint(getDartEntrypointFunctionName()) 
      .initialRoute(getInitialRoute()) 
      .appBundlePath(getAppBundlePath()) 
      .flutterShellArgs(FlutterShellArgs.fromIntent(intent)) 
      .handleDeeplinking(shouldHandleDeeplinking()) 
      .renderMode(renderMode) 
      .transparencyMode(transparencyMode) 
      .shouldAttachEngineToActivity(shouldAttachEngineToActivity()) 
      .build<FlutterFragment>() 
fragmentManager 
      .beginTransaction() 
      .add( 
           FRAGMENT_CONTAINER_ID, 
           flutterFragment, 
           TAG_FLUTTER_FRAGMENT 
          ) 
       .commit()
  • 平臺層和 Flutter 層通訊。不管是開發 Plugin 仍是業務邏輯,平臺層與 Flutter 層通訊是必不可少的,爲此就須要使用到MethodChannel。平臺層經過MethodChannel請求調用 Flutter 層 API 時,數據在通過打包編碼後,經過 JNI、DartVM 傳到 Flutter 層解碼後使用;待結果計算完成後,又會從新打包編碼,通過 DartVM、JNI 傳回到 Native 層;同理,在 Flutter 層請求調用平臺層的 API 時,數據處理是一致的,只是流轉方向相反。經過這種方式,平臺層與 Flutter 層就創建了一個雙向的、異步的通訊通道。在下面的示例代碼中,Native 層使用dev.flutter.example/counter建立一個MethodChannel,並設置 Handler 接收 Dart 的遠程方法調用 incrementCounter,並調用 reportCounter 將結果回傳。
channel = MethodChannel(flutterEngine.dartExecutor, "dev.flutter.example/counter") 
channel.setMethodCallHandler { call, _ -> 
     when (call.method) { 
         "incrementCounter" -> { 
              count++ 
              channel.invokeMethod("reportCounter", count) 
         } 
     } 
}

Dart 層使用相同的名稱建立 MethodChannel,並設置 Handler 處理回調結果,隨後調用 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(); 
    } 
  }

這裏咱們是經過手動建立 MethodChannel 進行通訊的,這在進行簡單通訊的場景是沒問題的,但在通訊接口 API 比較複雜的狀況就不是很適用了。

一是繁瑣,由於咱們須要手寫大量的打包、拆包代碼;二是容易出錯。這個時候就輪到 Pigeon 大顯身手了。Pigeon 是一個官方推出的代碼生成工具,能夠生成類型安全的雙向通訊 API 接口,具體能夠參考官方的 Example,這裏再也不贅述。

Pigeon :https://flutter.dev/docs/development/platform-integration/platform-channels#pigeon

Flutter APK 解析

到這裏,咱們已經瞭解瞭如何在現有 Android 項目中引入並使用 Flutter,接下來咱們再來探究一下 Flutter APK 的結構,看看 Flutter Tools 在這個 APK 包內到底打包了哪些東西。下面兩圖分別爲 Debub 模式和 Release 模式下構建出來的 Flutter APK 包結構,忽略了非 Flutter 相關的項。

Flutter APK 包結構

能夠看到兩個模式下的 APK 結構大體相同,說明以下:

  • lib/{arch}/libflutter.so:爲對應架構的 Flutter Engine 共享庫,負責 Flutter 渲染、JNI 通訊、DartVM。若是不須要對應架構的版本,經過 abiFilters 能夠 Exclude 掉。
  • lib/{arch}/libapp.so:只存在於 Release 模式下,共享庫中包含 Dart AOT 生成的二進制指令和數據。在運行時,Flutter Engine 經過 Dynamic Load 的方式,從共享庫中讀取對應的可執行機器指令以及數據。
  • assets/flutter_assets:Flutter 引用到的相關資源
    • fonts:包含字體庫。
  • FontManifest.json:引用到的字體庫清單文件,json 格式,全部使用到的字體、以及字體文件在 flutter_assets 下的路徑。
  • AssetManifest.json:其餘資源清單文件,json 格式,爲全部資源名稱到資源路徑的映射,Flutter 在加載某一項資源時,會經過這個配置清單找到對應路徑的資源進行讀取後加載。
  • kernel_blob.bin、isolate_snapshot_data、vm_snapshot_data:只存在於 Debug 模式下,分別爲 DartVM 字節碼與數據,其做用相似於 libapp.so,只是存在形式、打包方式不一樣。在 Debug 模式下,Flutter Tools 將指令和數據分別打包,主要是爲了熱重載(HotReload)服務的,而在 Release 模式下是統一打包成共享庫。

踩過的坑

這裏,也總結了幾個咱們在應用的時候遇到的問題,供你們參考避坑。

  • 路由管理複雜:這裏麪包括 Flutter 層內部的頁面路由管理以及 Flutter 與原生的混合棧管理。前者在 Navigator 2.0 API 中已經獲得了很好的完善與支持,但後者仍面臨着諸多限制與不足,須要改進。目前項目中還未涉及到後者這種很複雜的業務場景,所以對這一塊的研究比較少,感興趣的同窗能夠了解一下諸如 flutter_boost 此類的開源解決方案。
  • 生命週期不對應:Android 的組件通常都會有本身的生命週期,Flutter 的 Widget State 也有一套本身的生命週期,但這二者其實並非一一對應的。好比原生的 Activity 頁面雖然已經被 Finish 並 Destroy 掉了,但 Flutter 層的頁面並不必定會隨之而被 Dispose,尤爲是在使用 Cache Flutter Engine 的時候。Flutter 頁面是能夠脫離原生頁面而存在的,它們能夠被動態地 Attach 和 Detach,Attach 時會觸發從新渲染,Detach 時 UI 相關的全部操做都會 Pending 直到從新被 Attach。因此在混合開發中,業務邏輯不該該過分依賴 Widget State 的一些生命週期方法,由於它們可能會被延後執行從而致使一些奇怪的 Bug。

總結

Flutter 混合開發使得開發者能夠漸進式地進行 Flutter 開發與遷移,是 Flutter 寄生於原平生臺相當重要的一環。

本文主要從一個 Android 開發的視角,介紹了 Flutter 混合開發的入門知識。隨着 Flutter 開源項目的不斷迭代與演進,混合開發的體驗正在變得愈來愈好、性能也愈來愈高。但美中不足的是仍然有一些應用場景與問題並未獲得很好地完善與解決,好比 Flutter 多實例問題(咱們也將在本月的另外一篇文章中跟你們分享介紹咱們在實踐 Flutter 多實例遇到的問題與解決方案,敬請關注)。

瑕不掩瑜,Flutter 這門技術總體而言仍是很是不錯的,它現在仍處於快速發展的階段,相信在 Flutter 團隊與開源社區的共同努力下,將來的生態值得期待。

做者簡介

李成達,網易雲信資深移動端開發工程師,熱衷於研究跨平臺開發技術以及工程提效,目前主要負責視頻會議組件化 SDK 的相關研發工做。

關於網易雲信的技術實踐,也歡迎關注網易雲信官網

更多技術乾貨,歡迎關注【網易智企技術+】微信公衆號。

相關文章
相關標籤/搜索