Release Flutter的最後一公里

Release Flutter的最後一公里

來自專欄閑魚技術10 人贊了文章

作者:閑魚技術-凱航

Flutter是一個使用Dart語言開發的跨平台移動UI框架,通過自建繪製引擎,能高性能、高保真地進行Android和IOS開發。在業界還未出現過Base Flutter的大型商業應用實戰驗證的情況下,閑魚技術團隊在最複雜且重要的商品詳情頁作了相關的技術實踐並取得良好的結果。現嘗試通過本文向有興趣進行類似實踐的開發者或團隊分享過程中的思考/實踐過程。

Flutter特色

面對一系列移動開發技術:IOS、Android、Weex,RN, Kotlin,H5... Flutter究竟特色何在?

開發語言選擇

了解過Flutter的都知道,它採用Dart語言進行開發,而並非Java,Javascript這類熱門語言,這是Flutter團隊對當前熱門的10多種語言慎重評估後的選擇。因為Dart囊括了多數編程語言的優點,它更符合Flutter構建界面的方式。

Dart更多優勢可查看為什麼Flutter會選擇Dart

Flutter vs ReactNative框架對比

| ReactNative | Flutter|

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

|

|

|

ReactNative

  • 採用Javascript開發,需學React,成本高
  • 需要JavaScript橋接器,實現JS到Native轉化,性能耗損
  • 訪問原生UI,頻繁操作易出性能問題
  • 支持線上動態性,可有效避免頻繁更新版本

Flutter

  • 採用Dart開發,可直接編譯成Native代碼(易學)
  • 自帶UI組件和渲染器,僅依賴系統提供的Canvas(無橋接耗損)
  • 暫不支持線上動態性

Flutter更多特色可以鏈接為什麼說Flutter是革命性的?

每個框架都是為解決特定問題而產生的,不存在最好的框架,只有最適合你團隊的框架。閑魚是個業務快速發展的App,為更多業務嘗試和探索,它採用現有流行的框架,能支持線上動態化需求。但出於個性化交互以及流暢性體驗(首頁、商品詳情、發布閑置等),主鏈路依舊只採用原生開發。為兼顧跨端開發及高性能需求,閑魚經過充分調用,最終選擇了Flutter。為驗證Flutter的性能,閑魚挑選重要且複雜的主鏈路業務(商品詳情)作為首個Flutter頁面實踐點,以這種方式來快速暴露並解決Flutter相關問題。

閑魚Flutter突破點

Flutter與Native混合編程方案

隨著Flutter版本的不斷迭代,穩定性和質量逐漸完善,市場上純Flutter開發的App也不斷湧現。閑魚對Flutter採取「由點到面,逐一替換」 的策略,先將商品詳情遷移到Flutter頁面,後續逐步擴展到其他功能模塊,但這樣就不可避免涉及到Flutter與Native頁面混合調用的場景(如下圖):

對純Flutter工程而言,它主要通過FlutterView中Navigator來管理頁面間的跳轉;對純Native工程而言(如:android), 它主要通過系統中ActivityStackSupervisor類對頁面切換進行管理,這樣當Flutter與Native混合時,就面臨瀏覽一組頁面,兩套頁面管理方式(Flutter管理Flutter頁面,Native管理Native頁面), 若執行回退操作時,很難保證能回退到期望頁面。另外,Flutter工程中界面都是一個繼承自SurfaceView的FlutterView(說白了Flutter界面就一個View,不是Activity也不是Fragment),Flutter和Native組件間相互調用也不可避免。

| Flutter | Native(Android) |

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

|

|

|

因此要混合調用就會涉及兩個問題:

  • 混合棧管理
  • 組件間調用

混合棧管理

Flutter出現的目的旨在統一Android/IOS兩端編程,因此完全基於Flutter開發的App,只需提供一個包含FlutterView的頁面,後續頁面增加/刪除/跳轉均在FlutterView的Navigator中進行管理。但現在閑魚只是將部分模塊修改成Flutter開發,我們不可能為統一頁面棧管理而將其他所有頁面用Flutter重做一次,權衡成本與風險,亟需統一管理Native頁面和Flutter頁面跳轉交互的混合棧。為此,閑魚提出了4種解決方案(如下圖):

由於IOS有對外系統介面可以方便管理頁面棧,因此主動記錄頁面棧信息就可以解決混合棧管理(方案1),但Android任務棧由系統管理,且融合複雜的Activity回收機制,為降低android學習成本,google並沒有對外提供頁面棧管理API,方案1方式行不通。為統一android/IOS混合棧管理方式,從FlutterView上著手更為可靠,以此為引,閑魚提出兩種方式:

  1. 每啟動一個Activity就啟動一個新的FlutterView(方案4);
  2. 抽取單一FlutterView或FlutterNativeView,後續每啟動一個Activity都對FlutterView或FlutterNativeView進行復用(方案2或方案3);

考慮到每啟動一個頁面都新創建一套新的Flutter渲染機制,開銷過重,目前閑魚Flutter實踐採用方案2,相比而言,該方案性能相對穩定且易操作,下面就是否復用FlutterView進行對比,主要觀測Java內存和Native內存增加情況:

| 未復用FlutterView | 復用FlutterView |

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

|

|

|

數據表明:不復用FlutterView時平均打開一個頁面(空頁面),Java內存增長0.02M,Native內存增長0.73M;復用FlutterView時平均打開一個頁面(空頁面),Java內存增長0.019M,Native內存增長0.65M,因此,復用FlutterView在內存使用上是有優勢的,如需更深了解可查看Android Flutter內存初探。此外,相關方案的詳細表述在 How to manage page stack in flutter/native hybrid App 以及Support multiple shells in a single process均有闡述。

組件間調用

組件間採用比較常見場景就是黑屏問題,出現該問題多數為Layer衝突。從上圖(右)可知UI渲染原理:GPU的VSync信號同步到UI線程,UI線程使用Dart構建抽象的視圖結構(Layer Tree),接著在GPU線程進行圖層合成,且視圖數據提供給Skia引擎進行渲染生成GPU數據,最終通過OpenGL或Vulkan提供給GPU,由此可以看出Flutter並不關心顯示器、視頻控制器以及GPU具體工作細節,它只關心發出的VSync信號,以求儘可能快地在兩個VSync信號之間計算併合成視圖數據並提供給GPU。Flutter開發者都知道Flutter界面渲染時,使用的是FlutterViewController.view的Layer,倘若Flutter頁面跳轉到Native做界面渲染相關邏輯時, Native也使用同一個Layer,這將會導致Flutter在release模式無法渲染,LayerTree合成失敗即Layer衝突。不過這問題解決也很簡單,只需要採用Window或獨立View方式喚起Native即可。

解決了Flutter與Native混合編程所面臨的問題後,接下來要處理的就是混編工程問題,出現該問題的原因還是我們的項目不是完全的Flutter工程(即:android /ios + Flutter)所致。混合工程項目結構以及Flutter產物如下圖:

項目結構

Flutter產物

其實對一般Flutter工程而言,採用AndroidStudio編譯Flutter與編譯Native工程方式一樣,當將其部署到server端採用mtl編譯時,server缺少Flutter編譯環境,因而導致Flutter工程無法編譯。解決此問題可以採取兩種方式:

  1. 在每個server端部署Flutter編譯環境
  2. Native工程遠程依賴Flutter編譯產物

對1,對各server端都去部署Flutter環境有點不切實際(若server就那麼幾台也可以);對2,閑魚的做法是將Flutter工程編譯出的中間產物以AAR形式導出並上傳至maven庫,最後Native工程以依賴包形式將AAR打入最終apk中,這樣處理後解耦了Native團隊對Flutter團隊的依賴。當然,具體實踐過程中肯定沒有這麼簡單,我們在編譯過程中對Pod/Gradle編譯腳本、engine以及flutter_tools等均有所優化,對後續集團推廣Flutter奠定了基礎。

阿里Flutter生態適配

將Flutter應用於閑魚,不可避免需要使用集團提供的基礎組件庫,但這些組件庫都是Native,考慮到為後續Flutter在集團推廣,打造阿里Flutter生態圈,閑魚團隊對集團內部基礎組件庫做了適配支撐,後續可建立私有倉庫,直接Git引用。

生態適配原理及性能

上圖(左)概述了Flutter平台通道,使用MethodChannel在Client(UI)和主機(平台)之間傳遞消息,消息和響應非同步傳遞以確保用戶界面保持正常響應。對UI,Flutter的MethodChannel類可以發送與方法調用相對應的消息;對平台,Android端MethodChannel類和IOS端FlutterMethodChannel類可以接收方法調用並發送結果,同時方法調用還可以逆向發送,即以平台作為實現Dart方法的Client。值得注意的是Flutter Plugin開發相關原理也是如此。上圖(右)還對MethodChannel吞吐量性能進行了簡略表述。

多媒體解決方案

在以內容為王的時代,多媒體技術備受關注,性能的好壞直接影響用戶體驗,但Flutter多媒體默認功能存在以下缺陷:

  • 功能單一,如:播放器缺少濾鏡,與Native淘寶播放器存在一定差距
  • 兼容性欠佳

為改善體驗,優化性能,閑魚對Flutter播放器以及圖片性能作出如下改進:

Texture對接自定義視頻播放器

具體方案:

圖片性能優化

有過移動開發經驗的都知道,圖片展示是OOM出現的高頻場景,而Flutter默認採用基於LRU演算法的圖片緩存策略,且圖片緩存MaxSize=1000,佔用內存較高,為此閑魚採用以下兩種策略對圖片性能進行優化

Flutter 上線效果 & 性能對比 & 成熟度

解決了Flutter存在的問題,要的就是產品能夠以一種性能穩定、交互流暢、界面美觀的姿態呈現在用戶面前,下面就以閑魚寶貝詳情線上Flutter版本為引,對Flutter應用頁面展示、性能以及成熟度進行闡述。

閑魚寶貝詳情Flutter應用線上效果

Native性能對比

測試環境

  • 測試機型:iphone6
  • IOS版本:11.3
  • 閑魚版本: 6.1.3
  • 測試方法: 在Flutter版本和Native版本各自寶貝詳情頁面執行相同操作:即從我發布的頁面進行10個不同詳情頁面

註: 下圖僅對Flutter和Native性能進行了粗略對比

Flutter成熟度

上圖為crash收斂曲線圖,可容易看出經過幾個版本迭代,crash率基本趨於穩定,體感與Native可以媲美。

延展討論

目前Flutter尚處於Preview階段,沒有經過大規模實踐驗證,框架成熟度及穩定性仍有待完善。上文僅僅是將閑魚團隊在實踐Flutter開發時碰到部分問題及解決方案進行了簡略闡述,一個產品從開發到上線所面臨的問題,肯定遠不及這些。隨著Flutter覆蓋場景的增加,難題也會不斷湧現,健全有效的性能及穩定性監控體系不可或缺。為建設基於Flutter全新的一體化研發體系,提高開發效率,對動態化需求、規範Dart編碼、統一中間件橋接機制、快速發版能力及完備的自動化測試建設等一系列問題亟需解決,倘若您對此感興趣,歡迎一起交流學習~

簡歷投遞:guicai.gxy艾特alibaba-inc.com

推薦閱讀:

深入理解Flutter引擎線程模式
最近用 Flutter 做了幾個 APP
Android Studio 嘗試 Flutter Demo
Flutter 安裝
不要一知半解,深入理解flutter的編譯原理,好嗎

TAG:編程 | Flutter | ReactNative |