[閒魚技術] Release Flutter的最後一公里

閒魚技術發表於2018-07-11

作者:閒魚技術-凱航

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

Flutter特色

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

image.png

開發語言選擇

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

image.png
Dart更多優勢可檢視為什麼Flutter會選擇Dart

Flutter vs ReactNative框架對比

ReactNative Flutter
image.png
image.png

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頁面混合呼叫的場景(如下圖):

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

Flutter Native(Android)
image.png
image.png

因此要混合呼叫就會涉及兩個問題:

  • 混合棧管理
  • 元件間呼叫

混合棧管理

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

image.png
由於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記憶體增加情況:

[閒魚技術] Release Flutter的最後一公里
資料表明:不復用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均有闡述。

元件間呼叫

image.png
元件間採用比較常見場景就是黑屏問題,出現該問題多數為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產物如下圖:

專案結構

image.png

Flutter產物

image.png

其實對一般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引用。

生態適配原理及效能

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

多媒體解決方案

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

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

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

Texture對接自定義視訊播放器

image.png

具體方案:

image.png

圖片效能優化

有過移動開發經驗的都知道,圖片展示是OOM出現的高頻場景,而Flutter預設採用基於LRU演算法的圖片快取策略,且圖片快取MaxSize=1000,佔用記憶體較高,為此閒魚採用以下兩種策略對圖片效能進行優化

image.png

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

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

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

image.png

Native效能對比

測試環境

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

注: 下圖僅對Flutter和Native效能進行了粗略對比

image.png

Flutter成熟度

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

延展討論

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

簡歷投遞:guicai.gyx@alibaba-inc.com

相關文章