Flutter 是谷歌開發的一款能夠跨平臺開發的 UI框架,它的原理接近於遊戲引擎,目的在於統一Android/iOS 兩端開發,Flutter頁面有本身的棧,正常狀況下,若是一個App徹底由 Flutter構成,那麼只須要一個 FlutterView
便可。android
上述方案只適用於一些新構建的App,對於一些已有的App,是不可能用 flutter來重構的,成本太大,週期太長,因此這裏須要實現一套 Native 頁面棧和Flutter頁面棧的管理方案,即混合棧
。git
關於混合棧的管理,閒魚出過一篇文章,可是對於它的兼容性問題和截圖問題,沒有采用,不過做者對閒魚的混合棧源碼作了參考,這裏感謝閒魚的源碼分享。本文是在 android 的基礎上講解實現方式的,iOS 目前使用的仍是截圖方案,其他的原理差很少github
不進行任何處理,直接使用 FlutterActivity
來打開頁面:此方法最接近原生,交替打開幾個頁面後會呈現出如下頁面結構 緩存
每一個 FlutterActivity
都有本身的 flutter 棧,此時若是用戶點擊了返回按鈕的時候頁面退出的呈現形式是正常的,可是若是App用了側滑返回的話工做就會不正常。框架
側滑結束
FlutterActivit2
會一會兒結束三個 flutter widget 頁面async
除了上述問題外,還存在一個嚴重的問題:FlutterView1
和 FlutterView2
屬於兩個 isolate,二者至關於兩個 flutter engine 實例,在內存上隔離的,不共享函數
總結: 該方案有如下缺點佈局
FlutterView
之間沒法共享內存(圖片緩存,全局單例都不可公用)FlutterActivity
都會啓動一個新的 Flutter 實例全局共用一個 FlutterView
,每一個 flutter 頁面都有一個對應的 native 頁面:此方案能夠解決方案一中的內存共享浪費問題post
此方案的大體原理以下: 測試
關鍵步驟是 2 這個操做,當要打開一個新的 flutter 頁面時,native 會啓動一個新的 FlutterActivity
,而後把當前 FlutterActivity1 中的 FlutterView
移除,而且添加到 FlutterActivity2 中。
退出頁面的時候也同樣,先讓 FlutterView
從 FlutterActivity2 中 remove 移走,而後 add 到 FlutterActivity1 中。
你可能會想:「切換頁面的時候,FlutterView
從 FlutterActiviy 移除了,顯示不是會變成空白了嗎?」
什麼都不作,的確存在上述問題,這裏想把此方案實現,還須要考慮兩點:
FlutterView
從 FlutterActivity1 移除的時候,顯示的內容不會被移除FlutterView
從 FlutterActivity1 移除添加到 FlutterActivity2 的以前,必須保證新的 flutter page 已經 push 到 flutter 的棧中,不然 FlutterActivity2 顯示的仍是 FlutterActivity1 中顯示的界面這裏要實現第一點的話只能使用截圖方案,在
FlutterView
移除前先保存一份當前頁面的截圖快照,而後移除,這樣就不會出現空白的問題
全局共用一個 FlutterNativeView
,每一個 flutter 頁面都有一個對應的 native 頁面:此方案和方案二想接近,最大的區別就是複用的東西變成了 FlutterNativeView
此方案的結構圖以下:
和方案二不一樣的是,方案三中 FlutterView
和 FlutterActivity
綁定在一塊兒了,這樣能夠避免 FlutterView
單例化形成的 context
泄漏。
並且相比於方案二,要實現此方案只須要知足一條規則便可:
FlutterNativeView
從 FlutterActivity1 detach 而後 attach 到 FlutterActivity2 的以前,必須保證新的 flutter page 已經 push 到 flutter 的棧中,不然 FlutterActivity2 顯示的仍是 FlutterActivity1 中顯示的界面你會發現,這裏不須要 FlutterNativeView
在 detach 的時候構造一份當前頁面的快照而後佔位顯示.
由於在頁面切換的時候 FlutterView
並無從 FlutterActivity
中移除,當 FlutterNativeView
從 FlutterView
detach 的時候,FlutterView
顯示的內容就不會再更新了,至關於 Android 上的 onPreDraw
函數返回 false
, 因此這裏不必截圖保存快照。
通過上述方案的探索,決定在 android 上使用第三套方案
iOS 由於有側滑返回,沒法避免截圖,由於在側滑的時候,頁面不必定結束。因此我這裏拋棄了 android 上側滑返回(原本 android 的側滑返回就很奇怪,不支持合理)
實現關鍵點:
FlutterView
單 FlutterNativeView
實例FlutterNativeView
和 FlutterView
脫離,才能夠在 flutter 棧操做頁面的進退整個頁面啓動跳轉打大體流程圖以下:
左側是 flutter 的執行流程,右側是 android native activity 的執行流程
上述代碼的設計尚未考慮頁面之間的數據傳遞,原生 flutter 的頁面數據傳遞是這樣的:
void jumpToSettings(BuildContext context) async {
String result = await Navitor.of(context).pushNamed("settings");
print("page return: $result");
}
複製代碼
因此在設計頁面數據傳遞的時候向原生的看齊,以下所示:
final result = await VDRouter.instance().openUrlFromNative(
context: context,
routerOption: RouterOption(
url: "native://example", args: {"message": "Open from Flutter"}));
複製代碼
當 native 端的 MethodCallHandler
被調用時,有個參數是 result
,只有給這個 result
設置告終果 (result.success(xxx)
),上面的 await 纔會有返回,順着這個思路去實現很簡單。
只要 FlutterActivity
把由當前 flutter 發起打開頁面請求的 result
對象保存起來,而後調用 startActivityForResult
來啓動頁面,等頁面結束後會回調到 onActivityResult
中,此時再經過保存的 result
對象,把結果返回給 flutter 端。
傳參直接使用 intent 傳參便可。
每次啓動一個新的 FlutterActivity
都須要和 flutter 端同步下當前狀態欄的沉浸式狀態,這裏經過 native 主動調用 channel 來同步
// 請求更新主題色到 native 端,這裏使用了一個測試接口,之後要注意
var preTheme = SystemChrome.latestStyle;
if (preTheme != null) {
SystemChannels.platform.invokeMethod("SystemChrome.setSystemUIOverlayStyle", _toMap(preTheme));
}
複製代碼
FlutterNativeView
的 detach 和 attach 的時候,須要注意 FlutterActivity
的生命週期和 FlutterView
中 surface 的建立狀態,保證 FlutterActivity
和 FlutterView
的生命週期同步到 FlutterNativeView
總的來講,咱們微店基於上述理論實現了一個混合棧插件,沒有反射 flutter sdk,沒有內存泄漏,不須要截圖,支持頁面間的數據傳遞(源碼後續會開放),看似簡單實際實現過程當中仍是遇到過不少小問題的,好比頁面白屏,返回鍵無效之類的,這些都是 native 棧和 flutter 棧不一樣步致使的。
後續咱們微店混合棧的問題繼續跟進的問題以下:
Navigator.of(context).pop()
結合其中1.
的話目前沒有什麼好的思路,可是2.
、3.
、4.
點 已經有了想法,待實現驗證,敬請期待。
qigengxin,@WeiDian,2016年加入微店,目前主要負責微店App的基礎支撐開發工做。
微店App技術團隊
官方公衆號