好像拖延了兩個多月才更新博客,或許忙是理由,各種各樣的事情堆積起來似乎都處理不過來了,頭髮也白了一片。好不容易閒下來了,只想著狠狠睡一覺,睡醒了才想起來還有幾篇計劃要寫的博客。
對了,公眾號的名字改成了開水Kais,未來會更多地去聊一下人生的暢想,寫一些心情隨記,不再單單是寫技術類的文章。
上篇講了Native和Flutter端混合開發的交互方式,並由此引出了Flutter Boost的基本用法。本篇我們繼續對Flutter Boost進行深入了解。
在理解原理之前,我假設大家都對Flutter Boost的基本用法都已經了解,如果還沒有用過Flutter Boost,我建議大家先閱讀官方文檔以及看看example的代碼。
GitHub地址:https://github.com/alibaba/flutter_boost
概括一下,Flutter Boost在Android中使用的步驟如下:
調用FlutterBoost.singleton.registerPageBuilders註冊路由表FlutterBoost.singleton.addBoostNavigatorObserver註冊路由跳轉觀察者(可選)Flutter跳轉Android頁面:FlutterBoost.singleton.open(url)Android跳轉Flutter:Intent intent = BoostFlutterActivity.withNewEngine().url("url").params(params).build(context);本篇將會從以下幾個點來深入理解Flutter Boost的機制:
⚠️:基於v1.17.1-hotfixes分支
Flutter Boost架構基礎架構圖參考文檔《已開源|碼上用它開始Flutter混合開發——FlutterBoost》
我們從Flutter端和Android端來拆解Flutter Boost的整體架構。
Native層概念
Container:Native容器,平臺Controller,Activity,ViewController
Container Manager:容器的管理者
Adaptor:Flutter是適配層
Messaging:基於Channel的消息通信
Dart層概念
Container:Flutter用來容納Widget的容器,具體實現為Navigator的派生類
Container Manager:Flutter容器的管理,提供show,remove等Api
Coordinator: 協調器,接受Messaging消息,負責調用Container Manager的狀態管理
Messaging:基於Channel的消息通信
我把官方的架構圖細化了一下,加上自己的理解,重新整理出整體的架構圖如下:
上面為Native端的流程,以Android端為例(如不做特殊說明,以下內容皆以Android端的代碼為基準),Container即為BoostFlutterActivity或者FlutterFragment。按照Flutter Boost的設計理念,你可以把Container比作Webview,用來承載Flutter頁面。
Delegate主要負責FlutterView的創建,使用代理模式,將Container的共同邏輯解藕。
Native端和Flutter端通過MethodChannel通信,FlutterBoostPlugin負責method的調度。Native端的Container初始化完成後,會發送消息通知Flutter端展示頁面。
Flutter端的ContainerCoordinator負責消息處理,當接收到Native端展示頁面的消息後,FlutterContainerManager會創建BoostContainer,並根據路由找到相應頁面加載到BoostContainer中。
BoostContainerNative端加載容器在Android側,結合項目的路由框架,然後定義Flutter跳轉的schema協議,比如flutter://page,當調用Router.openPage("flutter://page")時(假如你的路由框架是這麼設定的),路由層判定為跳轉Flutter頁面,則調用BoostFlutterActivity.withNewEngine().url("url").params(params).build(context)就可以打開Flutter頁面了。
我們從該入口方法開始,這裡其實就是創建並打開了BoostFlutterActivity。我們知道BoostFlutterActivity為Native容器,那麼問題來了,打開BoostFlutterActivity後是如何加載Flutter頁面的?很顯然,頁面的加載與Activity的生命周期相關,從Activity的onCreate開始,看看在BoostFlutterActivity整個加載過程中都做了哪些工作。
onCreate:在該方法中創建了FlutterActivityAndFragmentDelegate,最終調用到Delegate的onCreateView方法創建FlutterView。
FlutterActivityAndFragmentDelegate#onCreateView:在該方法中實例化了mSyncer,實力化對象為ContainerRecord,ContainerRecord實現了IContainerRecord、IOperateSyncer接口。ContainerRecord關聯了對應的Flutter頁面,將Native容器與Flutter容器的生命周期同步。
下面一張圖表示Native端容器啟動時,View的層級關係:前面說過,mSyncer的實現類為ContainerRecord,當調用到ContainerRecord的onCreate()方法,可以看到最後還是通過mProxy執行了create()方法:
最終交給MethodChannelProxy代理類來調用與Flutter端通信的方法,MethodChannelProxy是ContainerRecord的內部類。以上步驟中就是執行了proxy的create()方法。可以看到這裡是調用了invokeChannelUnsafe方法,最終通過FlutterBoost.instance().channel().invokeMethod調用Plugin層的方法與原生進行交互。
後面MethodChannel的調用流程不細述,我們可以追著代碼看到原生端的方法是如何被調用的。
Flutter端加載流程:簡單來說,從Flutter側執行FlutterBoost.open方法打開頁面到FlutterBoost容器打開這個過程,主要經歷了以下階段:
單引擎模式《已開源|碼上用它開始Flutter混合開發——FlutterBoost》 文中講到Flutter Boost主要解決的一個痛點就是在混合棧模式下多頁面跳轉時,Flutter底層會創建多個引擎從而導致內存暴增的問題。因此Flutter Boost解決該問題的思路為:
閒魚目前採用的混合方案是共享同一個引擎的方案。這個方案基於這樣一個事實:任何時候我們最多只能看到一個頁面,當然有些特定的場景你可以看到多個ViewController,但是這些特殊場景我們這裡不討論。我們可以這樣簡單去理解這個方案:我們把共享的Flutter View當成一個畫布,然後用一個Native的容器作為邏輯的頁面。每次在打開一個容器的時候我們通過通信機制通知Flutter View繪製成當前的邏輯頁面,然後將Flutter View放到當前容器裡面。
其實看到這裡,我們大概也能猜到Flutter Boost的實際就是在實現一個類似於WebContainer的容器,所有Flutter的頁面都在該容器中進行渲染展示。
最後我們已經梳理了Flutter Boost的基本架構和核心流程,但是由於文章篇幅的原因,無法在一篇文章中詳細地整理所有源碼的內容,只能列出幾個核心的關鍵方法,大家可以嘗試自己看著源碼再梳理一遍流程,理解其原理,其實最重要的是了解該框架的設計思路。
在寫這篇文章之前,我是剛開始接觸到Flutter混合項目的開發才了解到Flutter Boost這個框架,剛開始使用這個框架的時候官方文檔並沒有完善,很多時候都得靠自己去閱讀源碼尋找問題解決的方法,並苦於各種奇奇怪怪的問題導致項目中四處碰壁,因此我對於Flutter Boost這個框架一直抱以比較排斥的態度。幾天前看到官方發布了3.0的版本,我對這個框架又恢復了一些信心,至少可以看到阿里對於這個開源庫的態度是真心地想要發展起來並以此來解決大部分的混合棧場景的。
v3.0-beta.1
1.flutter sdk升級不需要升級boost
2.簡化架構
3.簡化接口
4.雙端接口設計統一
5.解決了top issue
6.android不需要區分androidx 和support
但是我們真的需要Flutter Boost嗎?剛開始我選擇Flutter Boost的初衷是為了解決多引擎模式下導致的內存問題,可是在使用的過程中遇到的種種問題,最終不得不放棄該框架。
其實為了解決多引擎模式下多Engine導致的內存問題,官方是有提供解決方案的:使用緩存的 FlutterEngine https://flutter.cn/docs/development/add-to-app/android/add-flutter-screen?tab=cached-engine-activity-launch-kotlin-tab#step-3-optional-use-a-cached-flutterengine
每一個 FlutterActivity 默認會創建它自己的 FlutterEngine。每一個 FlutterEngine 會有一個明顯的預熱時間。這意味著加載一個標準的 FlutterActivity 時,在你的 Flutter 交互頁面可見之前會有一個短暫的延遲。想要最小化這個延遲時間,你可以在抵達你的 FlutterActivity 之前,初始化一個 FlutterEngine,然後使用這個已經預熱好的 FlutterEngine。
在1.26版本之後,官方還提供了FlutterEngineGroup這個API來創建Flutter Engine:多個 Flutter 頁面或視圖 https://flutter.cn/docs/development/add-to-app/multiple-flutters
目前在 Android 和 iOS 上,除了第一個 Flutter 實例以外,其他每一個實例的內存佔用量大約為 180kB。
Flutter官方一直在傾聽開發者的訴求(針對混合棧開發的問題,官方開了一個問題集:issues#72009 https://github.com/flutter/flutter/issues/72009 ),這一點是我堅持學習Flutter的原因,也正因如此我對Flutter的未來是充滿信心的。
至此,探索Flutter Boost框架到此就告一段落了,我欣賞該框架的設計思路,但由於種種問題,我可能並不會在項目中選擇使用Flutter Boost,而會考慮基於Flutter自身的框架去實現混合棧架構,因為其相較於自主實現View容器的Flutter boost穩定得多。