開源地址: https://github.com/alibaba/fl...
具備必定規模的App一般有一套成熟通用的基礎庫,尤爲是阿里系App,通常須要依賴不少體系內的基礎庫。那麼使用Flutter從新從頭開發App的成本和風險都較高。因此在Native App進行漸進式遷移是Flutter技術在現有Native App進行應用的穩健型方式。閒魚在實踐中沉澱出一套本身的混合技術方案。在此過程當中,咱們跟Google Flutter團隊進行着密切的溝通,聽取了官方的一些建議,同時也針對咱們業務具體狀況進行方案的選型以及具體的實現。git
Flutter技術鏈主要由C++實現的Flutter Engine和Dart實現的Framework組成(其配套的編譯和構建工具咱們這裏不參與討論)。Flutter Engine負責線程管理,Dart VM狀態管理和Dart代碼加載等工做。而Dart代碼所實現的Framework則是業務接觸到的主要API,諸如Widget等概念就是在Dart層面Framework內容。github
一個進程裏面最多隻會初始化一個Dart VM。然而一個進程能夠有多個Flutter Engine,多個Engine實例共享同一個Dart VM。編程
咱們來看具體實現,在iOS上面每初始化一個FlutterViewController就會有一個引擎隨之初始化,也就意味着會有新的線程(理論上線程能夠複用)去跑Dart代碼。Android相似的Activity也會有相似的效果。若是你啓動多個引擎實例,注意此時Dart VM依然是共享的,只是不一樣Engine實例加載的代碼跑在各自獨立的Isolate。瀏覽器
引擎深度共享緩存
在混合方案方面,咱們跟Google討論了可能的一些方案。Flutter官方給出的建議是從長期來看,咱們應該支持在同一個引擎支持多窗口繪製的能力,至少在邏輯上作到FlutterViewController是共享同一個引擎的資源的。換句話說,咱們但願全部繪製窗口共享同一個主Isolate。數據結構
但官方給出的長期建議目前來講沒有很好的支持。框架
多引擎模式工具
咱們在混合方案中解決的主要問題是如何去處理交替出現的Flutter和Native頁面。Google工程師給出了一個Keep It Simple的方案:對於連續的Flutter頁面(Widget)只須要在當前FlutterViewController打開便可,對於間隔的Flutter頁面咱們初始化新的引擎。性能
例如,咱們進行下面一組導航操做:學習
Flutter Page1 -> Flutter Page2 -> Native Page1 -> Flutter Page3
咱們只須要在Flutter Page1和Flutter Page3建立不一樣的Flutter實例便可。
這個方案的好處就是簡單易懂,邏輯清晰,可是也有潛在的問題。若是一個Native頁面一個Flutter
頁面一直交替進行的話,Flutter Engine的數量會線性增長,而Flutter Engine自己是一個比較重的對象。
多引擎模式的問題
所以,綜合多方面考慮,咱們沒有采用多引擎混合方案。
前面咱們提到多引擎存在一些實際問題,因此閒魚目前採用的混合方案是共享同一個引擎的方案。這個方案基於這樣一個事實:任什麼時候候咱們最多隻能看到一個頁面,固然有些特定的場景你能夠看到多個ViewController,可是這些特殊場景咱們這裏不討論。
咱們能夠這樣簡單去理解這個方案:咱們把共享的Flutter View當成一個畫布,而後用一個Native的容器做爲邏輯的頁面。每次在打開一個容器的時候咱們經過通訊機制通知Flutter View繪製成當前的邏輯頁面,而後將Flutter View放到當前容器裏面。
老方案在Dart側維護了一個Navigator棧的結構。棧數據結構特色就是每次只能從棧頂去操做頁面,每一次在查找邏輯頁面的時候若是發現頁面不在棧頂那麼須要往回Pop。這樣中途Pop掉的頁面狀態就丟失了。這個方案沒法支持同時存在多個平級邏輯頁面的狀況,由於你在頁面切換的時候必須從棧頂去操做,沒法再保持狀態的同時進行平級切換。
舉個例子:有兩個頁面A,B,當前B在棧頂。切換到A須要把B從棧頂Pop出去,此時B的狀態丟失,若是想切回B,咱們只能從新打開B以前頁面的狀態沒法維持住。這也是老方案最大的一個侷限。
如在pop的過程中,可能會把Flutter 官方的Dialog進行誤殺。這也是一個問題。
並且基於棧的操做咱們依賴對Flutter框架的一個屬性修改,這讓這個方案具備了侵入性的特色。這也是咱們須要解決的一個問題。
具體細節,你們能夠參考老方案開源項目地址:
https://github.com/alibaba-flutter/hybrid_stack_manager
在閒魚推動Flutter化過程中,更加複雜的頁面場景逐漸暴露了老方案的侷限性和一些問題。因此咱們啓動了代號FlutterBoost(向C++ Boost致敬)的新混合技術方案。此次新的混合方案咱們的主要目標有:
跟老方案相似,新的方案仍是採用共享引擎的模式實現。主要思路是由Native容器Container經過消息驅動Flutter頁面容器Container,從而達到Native Container與Flutter Container的同步目的。咱們但願作到Flutter渲染的內容是由Naitve容器去驅動的。
簡單的理解,咱們想作到把Flutter容器作成瀏覽器的感受。填寫一個頁面地址,而後由容器去管理頁面的繪製。在Native側咱們只須要關心若是初始化容器,而後設置容器對應的頁面標誌便可。
Native層概念
Dart層概念
關於頁面的理解
在Native和Flutter表示頁面的對象和概念是不一致的。在Native,咱們對於頁面的概念通常是ViewController,Activity。而對於Flutter咱們對於頁面的概念是Widget。咱們但願可統一頁面的概念,或者說弱化抽象掉Flutter自己的Widget對應的頁面概念。換句話說,當一個Native的頁面容器存在的時候,FlutteBoost保證必定會有一個Widget做爲容器的內容。因此咱們在理解和進行路由操做的時候都應該以Native的容器爲準,Flutter Widget依賴於Native頁面容器的狀態。
那麼在FlutterBoost的概念裏說到頁面的時候,咱們指的是Native容器和它所附屬的Widget。全部頁面路由操做,打開或者關閉頁面,實際上都是對Native頁面容器的直接操做。不管路由請求來自何方,最終都會轉發給Native去實現路由操做。這也是接入FlutterBoost的時候須要實現Platform協議的緣由。
另外一方面,咱們沒法控制業務代碼經過Flutter自己的Navigator去push新的Widget。對於業務不經過FlutterBoost而直接使用Navigator操做Widget的狀況,包括Dialog這種非全屏Widget,咱們建議是業務本身負責管理其狀態。這種類型Widget不屬於FlutterBoost所定義的頁面概念。
理解這裏的頁面概念,對於理解和使用FlutterBoost相當重要。
前面咱們提到老方案在Dart層維護單個Navigator棧結構用於Widget的切換。而新的方案則是在Dart側引入了Container的概念,再也不用棧的結構去維護現有的頁面,而是經過扁平化key-value映射的形式去維護當前全部的頁面,每一個頁面擁有一個惟一的id。這種結構很天然的支持了頁面的查找和切換,再也不受制於棧頂操做的問題,以前的一些因爲pop致使的問題迎刃而解。同時也再也不須要依賴修改Flutter源碼的形式去進行實現,除去了實現的侵入性。
那這是如何作到的呢?
多Navigator的實現
Flutter在底層提供了讓你自定義Navigator的接口,咱們本身實現了一個管理多個Navigator的對象。當前最多隻會有一個可見的Flutter Navigator,這個Navigator所包含的頁面也就是咱們當前可見容器所對應的頁面。
Native容器與Flutter容器(Navigator)是一一對應的,生命週期也是同步的。當一個Native容器被建立的時候,Flutter的一個容器也被建立,它們經過相同的id關聯起來。當Native的容器被銷燬的時候,Flutter的容器也被銷燬。Flutter容器的狀態是跟隨Native容器,這也就是咱們說的Native驅動。由Manager統一管理切換當前在屏幕上展現的容器。
咱們用一個簡單的例子描述一個新頁面建立的過程:
這就是一個新頁面建立的主要邏輯,銷燬和進入後臺等操做也相似有Native容器事件去進行驅動。
目前FlutterBoost已經在生產環境支撐着在閒魚客戶端中全部的基於Flutter開發業務,爲更加負複雜的混合場景提供了支持。同時也解決了一些歷史遺留問題。
咱們在項目啓動之初就但願FlutterBoost可以解決Native App混合模式接入Flutter這個通用問題。因此咱們把它作成了一個可複用的Flutter插件,但願吸引更多感興趣的朋友參與到Flutter社區的建設。咱們的方案可能不是最好的,這個方案距離完美還有很大的距離,咱們但願經過多分享交流以推進Flutter技術社區的發展與建設。咱們更但願看到社區可以涌現出更加優秀的組件和方案。
在有限篇幅中,咱們分享了閒魚在Flutter混合技術方案中積累的經驗和代碼。歡迎興趣的同窗可以積極與咱們一塊兒交流學習。
在兩個Flutter頁面進行切換的時候,由於咱們只有一個Flutter View因此須要對上一個頁面進行截圖保存,若是Flutter頁面多截圖會佔用大量內存。這裏咱們採用文件內存二級緩存策略,在內存中最多隻保存2-3個截圖,其他的寫入文件按需加載。這樣咱們能夠在保證用戶體驗的同時在內存方面也保持一個較爲穩定的水平。
頁面渲染性能方面,Flutter的AOT優點展露無遺。在頁面快速切換的時候,Flutter可以很靈敏的相應頁面的切換,在邏輯上創造出一種Flutter多個頁面的感受。
項目開始的時候咱們基於閒魚目前使用的Flutter版本進行開發,然後進行了Release 1.0兼容升級測試目前沒有發現問題。
只要是集成了Flutter的項目均可以用官方依賴的方式很是方便的以插件形式引入FlutterBoost,只須要對工程進行少許代碼接入便可完成接入。
詳細接入文檔,請參閱GitHub主頁官方項目文檔。
目前,新一代混合棧已經在閒魚全面應用。咱們很是樂意將沉澱的技術回饋給社區。歡迎你們一塊兒貢獻,一塊兒交流,攜手共建Flutter社區。
項目開源地址:https://github.com/alibaba/flutter_boost
本文爲雲棲社區原創內容,未經容許不得轉載。