Flutter 工程化框架選擇 — 混合開發的摸爬滾打

語言: CN / TW / HK

theme: smartblue

本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!

這是 《Flutter 工程化框架選擇》 系列的第四篇 ,就像之前說的,這個系列只是單純告訴你,建立一個 Flutter 工程,或者說搭建一個 Flutter 工程腳手架,應該如何快速選擇適合自己的功能模組,可以說這是一個指引系列,所以比較適合新手同學。

本篇將著重介紹混合開發裡的一些技術選型,順便額外科普一些坑,看到後面你就會明白為什麼要做這一期分類,算是相對偏技術的一期

在 Flutter 裡進行混合開發一直都是“痛點”,其中最主要的原因就是:Flutter 有自己獨立的渲染引擎,因為 Flutter 控制元件渲染脫離了平臺控制元件的 render tree,這也造成了 Flutter 在混合開發上的實現相對複雜了不少

比較形象的理解:類似於把原生控制元件塞到 WebView 裡渲染。

事實上在 Flutter 裡混合開發也分為兩種:在 Flutter 裡混合原生平臺控制元件PlatformView)和將 Flutter 混合到原生平臺專案(add-to-app,今年我們的主題是 PlatformView ,這其中又以 Android 的 PlatformView 混合原生控制元件“故事最多”,當然今天我們不是從頭去講解它的技術實現,對歷史技術實現感興趣的可以看之前的文章,今天主要是聊如何去“選“。

PlatformView

在 Flutter 裡混合原生平臺的控制元件需要通過 PlatformView 進行接入實現,在這方面 iOS 的實現方式一直變化不大,但是在 Android 上不同版本到目前就出現了三種實現邏輯,所以本篇主要介紹 Android 平臺如何選擇合適的 PlatformView 方案。

拋開最早期如 flutter_webview_plugin 這種直接進行覆蓋原生控制元件的實現不談, PlatformView 在 Android 上目前官方提供的就有三種實現方式:

  • VirtualDisplays :類似於一個虛擬顯示區域,將虛擬顯示區域的內容渲染在一個記憶體 Surface上,然後同步紋理到 Engine ,事實上控制元件並不在渲染位置。

  • HybridComposition : 通過 addView 直接新增原生平臺控制元件,需要層級覆蓋時利用 FlutterImageView 承載 Flutter 控制元件的渲染進行堆疊,原生控制元件會在渲染位置。

  • TextureLayer :在渲染位置通過原生的透明 PlatformViewWrapper 做容器,然後通過替換 Canvas 將原生控制元件的紋理渲染到特定 Surface 上,控制元件不在渲染位置,但是可以通過父容器做事件攔截。

是不是看完有點懵?不怕後面會有對應的例子解釋,不理解也不要緊,這一篇是教你如何選,所以也並不需要真的理解內部是如何實現。

但是上面介紹的這三種實現也是有前提條件的,比如:

  • Flutter 1.2 之前,你只能用 VirtualDisplays
  • Flutter 3.0 之前,你只能用 VirtualDisplaysHybridComposition
  • Flutter 3.3 之前,你只能用 TextureLayerHybridComposition
  • Flutter 3.3 開始,你能用 VirtualDisplaysTextureLayerHybridComposition

是不是覺得有些奇怪,為什麼會有奇怪的排列方式?這裡面的故事線大概是這樣的:

  • 因為 VirtualDisplays 存在觸控事件和鍵盤等問題,所以通過 addView 進行原生堆疊的 HybridComposition 出現,解決了手勢和鍵盤等問題
  • 因為 HybridComposition 存在一些場景缺陷,比如列表卡頓,渲染執行緒不同步導致閃動等問題,所以 VirtualDisplays 得以繼續服役
  • 3.0 開始預設 VirtualDisplaysHybridComposition 被替換為 TextureLayer ,但是 HybridComposition 實現依舊保留,在使用時需要顯式執行 PlatformViewsService.initExpensiveAndroidView 才能使用,因為 TextureLayer 存在沒辦法替代原生控制元件 Canvas 的場景,同時 TextureLayer 要求 Flutter Mini SDK API 23
  • 3.3 開始 VirtualDisplays 實現又回來了,因為需要支援 Mini SDK API 23 以下的場景,同時比如 SurfaceView 機制原因,這類場景 TextureLayer 實現沒辦法直接相容,所以 3.3 開始在遇到 SurfaceView 實現時 TextureLayer 會切換為 VirtualDisplays 模式

這裡面固然有實現成本的問題,同時也有社群把控的問題,才造成了這樣“混亂”的局面

上面這麼說可能有些抽象,我們再看具體例子,首先看官方的 webview_flutter 實現, 如下表格所示,最初的時候 WebView 外掛預設是使用了 AndroidWebView ,也就是 VirtualDisplays 相關的實現;後來有了 HybridComposition 之後就新增了 SurfaceAndroidWebView ,使用者在需要時可以根據場景自行切換。

| 預設判斷 | VirtualDisplays | HybridComposition | | ----------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- | | | | |

這裡就不得不提以前的 issue ,如果你的 WebView 存在觸控或者鍵盤問題就可以通過下述程式碼來解決,因為正如前面說過, HybridComposition 是通過 addView 的方式新增原生控制元件,所以能儘可能儲存控制元件本身的特性。

dart if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();

而後來 webview_flutter 在更新迭代裡預設實現裡也修改為了 SurfaceAndroidWebView ,因為 HybridComposition 確實更適合 WebView 的場景,比如 H5 的詳情頁。

| 預設判斷 | TextureLayer | HybridComposition | | ----------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------- | | | | |

上面的例子可能還不夠典型,接著我們再看一個典型的例子: MapView ,如下所示,目前國內 Flutter 裡常見的地圖實現有:

雖然他們都是採用了 PlatformView 的實現,但是選擇的實現方式卻存在一定差異,而通過這些差異也可以很好比對 PlatformView 實現的區別:

  • 高德 Flutter 外掛採用的是 AndroidView 的實現方式,也就是在 3.0 以下是 VirtualDisplays ,在 3.0 以上是 TextureLayer , 從表中的圖 2 也可以看到在 Flutter 3.0 MapView 父容器對應的 TextureLayerPlatformViewWrapper

| dart 實現 | TextureLayer | | ----------------------------------------------------------- | ----------------------------------------------------------- | | | |

這裡額外需要注意的是:由於隱私政策等原因,現在基本地圖框架都需要做一定的隱私相關適配,高德也不例外,所以如果想正常使用 SDK ,就需要注意初始化時的適配問題。

對隱私和許可權問題感興趣可以看 《2022 年 App 上架稽核問題集錦,全面踩坑上線不迷路》

  • 華為 Flutter 地圖外掛選擇的則是 HybridComposition 的實現方式,注意下圖程式碼裡的 initExpensiveAndroidView ,因為現在使用 PlatforViewLink 已經不能作為是否使用 HybridComposition 的判定標準,3.0 開始必須顯式配置了 initExpensiveAndroidView 才是 HybridComposition

| 3.0之前 PlatforViewLink | 3.0之後 PlatforViewLink + initSurfaceAndroidView | 3.0之後 PlatforViewLink + initExpensiveAndroidVie | | -------------------------- | ------------------------------------------------------ | ------------------------------------------------------- | | HybridComposition | TextureLayer | HybridComposition |

HybridComposition 詳細實現感興趣的可以瞭解:混合開發下 HybridComposition 和 VirtualDisplay 的實現與未來演進《1.20 下的 Hybrid Composition 深度解析》

  • 百度 Flutter 地圖外掛採用的是 AndroidView 的實現方式,但是原生端提供了 TextureViewSurfaceView 兩種實現選擇,這又產生了不同的組合結果。

| 3.0之前 | 3.0 | 3.0之後 | | ----------------- | ---------------------------------------------- | ------------------------------------------------------------ | | VirtualDisplays | TextureLayerSurfaceView 方案無法工作) | VirtualDisplays(SurfaceView)/ TextureLayer(TextureView) |

從下方的 Layout Inspector 可以看到,在 Flutter 3.3 下,百度地圖外掛如果使用了 MapSurfaceView 就會採用 VirtualDisplays ,而如果使用了 MapTextureView 就會採用 TextureLayer ,這是為什麼呢

| SurfaceView | TextureView | | ----------------------------------------------------------- | ----------------------------------------------------------- | | | |

前面我們不是說過,Flutter 在 3.0 的時候通過 TextureLayer 取代了 VirtualDisplays ,而 TextureLayer 的實現方式就是通過 PlatformViewWrapper 來替換 child 的 Canvas 從而提取紋理,但是在某些場景下就會有問題,例如 SurfaceView

簡單來說,SurfaceView 是比較特殊的場景,本身它具備雙緩衝機制,類似於兩個 Canvas ,同時在 WMS 有獨立的 WindowState, 所以渲染邏輯其實是脫離了原本的 View hierachy ,因此 Flutter 3.0 下的 TextureLayer 的 hook Canvas 實現沒辦法相容。

所以在 Flutter 3.3 中 VirtualDisplays 又回來了,作為多年服役的"老將",當執行平臺低於 23 或者原生控制元件是 SurfaceView 時,就會繼續降級採用 VirtualDisplays 的相關實現,這也算是折中的實現

| | | | ----------------------------------------------------------- | ----------------------------------------------------------- |

如果對這部分感興趣的可以查閱 Flutter 3.0下的混合開發演進 / Flutter 深入探索混合開發的技術演進

所以現在知道為什麼要做這一期了吧?Flutter 的 PlatformView 實現因為歷史包袱留下了很多“坑”,不理清楚這些坑,很可能就會讓你在遇到問題時找不到方向,總結一下:

  • VirtualDisplays 老驥伏櫪,採用的是虛擬顯示提取紋理的方式,可能存在手勢和鍵盤問題,適合列表和小控制元件場景,在 3.0 版本中缺席
  • HybridComposition 在 1.2 版本之後出現,因為是通過 addView 和層級堆疊實現混合,能儘可能保證控制元件的“原汁原味”,但是效能開銷會比較大,不適合列表裡使用,在 3.0 之後需要顯式呼叫 initExpensiveAndroidView
  • TextureLayer 在 3.0 版本開始出現,通過 Hook 住控制元件 Canvas 的方式實現紋理提取,但是需要 miniSDK 23 ,適用於替代 VirtualDisplays 的場景,但是在 3.3 開始遇到 SurfaceView 時會降級為 VirtualDisplays

其實我有預感,在之後的版本這部分邏輯還會出現變化,但是可能 3.0 和 3.3 的釋出一樣,並不會將這種變更放在 release note 裡。

最後,這裡額外介紹一個關於地圖的小知識點,現在國內三大地圖 SDK 廠商都有商用 5 萬起步的授權費用要求,如果你打算用在公司的專案裡,那麼這個是逃不掉的,因為遲早平臺會打電話聯絡你繳費,所以在評估時最好將這個費用問題和產品溝通下。

| 高德 | 百度 | 騰訊 | | ----------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------ | | | | | | | | image-20221009093013634 |

當然,華為的地圖服務目前看起來沒有商業授權的要求,但是它打包配置麻煩,特別是需要 HMS 的場景,所以如果為了省點錢又不怕麻煩,也可以考慮華為地圖。

那有人就要說了,我自己做一個行不行?我比較牛,有資料有能力自己做個簡易版地圖不就免費了?答案時不行,如下所示,應用平臺會有關於地圖相關的資質要求,如果沒有你做出來了也上不去。

稽核意見:我們發現您的應用內含有網際網路地圖服務相關內容,依據《關於加強網際網路地圖管理工作的通知》網際網路地圖服務單位應當依法取得相應的網際網路地圖服務測繪資質,並在資質許可的範圍內提供網際網路地圖服務。 ­修改建議:請補充提供《測繪資質證書》並在專業範圍中註明網際網路地圖服務;若接入第三方開放平臺地圖服務的,可提供雙方合作協議(授權證明),同時需在地圖顯著位置補充標明合作單位。

好了,本篇就到這裡結束了,可能相對而言會比之前的內容更“技術性”一些,但是本質還是希望可以幫助大家搞清楚 PlatformView 在選型上的區別,最後,如果你還有什麼關於 Flutter 工程或者框架的疑問,歡迎留言評論,