閑魚基于Flutter的移動端跨平臺應用實踐

IsobelCovin 6年前發布 | 293K 次閱讀 移動開發 Flutter

閑魚為什么使用 Flutter

Flutter 作為 Google 新一代的跨平臺框架,有較多的優點,但跟其他跨平臺解決方案相比,最吸引我們的是它的高性能,可以輕松構建更流暢的 UI。雖然各跨平臺方案都有各自的特點,但 Flutter 的出現,給閑魚、給大家都提供了一種新的可能性。

那么,Flutter 為什么會有高性能呢?

閑魚基于Flutter的移動端跨平臺應用實踐

首先,Flutter 自建了一個繪制引擎,底層是由 C++ 編寫的引擎,負責渲染,文本處理,Dart VM 等;上層的 Dart Framework 直接調用引擎。避免了以往 JS 解決方案的 JS Bridge、線程跳躍等問題。

第二,引擎基于 Skia 繪制,操作 OpenGL、GPU,不需要依賴原生的組件渲染框架。

第三,Dart 的引入,是 Flutter 團隊做了很多思考后的決定,Dart 有 AOT 和 JIT 兩種模式,線上使用時以 AOT 的方式編譯成機器代碼,保證了線上運行時的效率;而在開發期,Dart 代碼以 JIT 的方式運行,支持代碼的即時生效(HotReload),提高開發效率。

第四,Flutter 的頁面和布局是基于 Widget 樹的方式,看似不習慣,但這種樹狀結構解析簡單,布局、繪制都可以單次遍歷完成計算,而原生布局往往要往復多次計算,“simple is fast”的設計效果。

下面截圖是目前閑魚已經上線的商品詳情頁面:

閑魚基于Flutter的移動端跨平臺應用實踐

商品詳情頁包含混合棧、視頻、動畫、原生組件、多圖、留言蓋樓等功能,頁面較復雜,有代表性,也是閑魚最重要的頁面之一。選擇商品詳情頁做為第一個 Flutter 頁面,是閑魚能成功快速使用起 Flutter 的重要因素。

接下來介紹一下,閑魚的實踐過程和總結。

Flutter 與 Native 混合開發實踐

Flutter Hybrid 工程實踐(研發時)

我們把 Flutter 和閑魚現有的 APP 做漸進式的整合,App 中會同時有 Native、Flutter 和 H5 頁面。現有的 Flutter Demo 和應用,都是獨立的 Flutter 應用,而當把它和 Native 混合的時候,會碰到很多的困難。

首先是研發時的問題,怎么讓 Flutter 在現有的 Native 工程中開發起來。這個要從這張圖說起:

閑魚基于Flutter的移動端跨平臺應用實踐

閑魚 Flutter 工程結構如圖,三個藍色背景的目錄分別是安卓工程、iOS 工程和 main.dart 入口。編譯產物中以 iOS 為例,APP Framework 是 Flutter 應用頁面代碼,Flutter Framework 是 Flutter 引擎。

這個過程,需要重點考慮幾個問題:如何基于現有工程搭建混合工程?如何支持過渡期的 Flutter 開發及純 Native 開發的雙開發模式?如何讓 Flutter 與現有持續集成、構建工具集成?

閑魚基于Flutter的移動端跨平臺應用實踐

首先,現有的 Native 工程并不符合 Flutter 默認的規范,兩者不能完全匹配,需要修改打包腳本,甚至修改 Flutter 的打包 Tool 來解決。另外,我們通過 Submodule 將現有?程引入到 Flutter 父工程中。

閑魚基于Flutter的移動端跨平臺應用實踐

純 Native 開發同學,不需要引入 Flutter 工程,直接在 iOS 或 Android 工程下開發,Flutter 以產物的方式集成到 Native 中運行,Flutter 的開發同學引入 Submodule。

閑魚基于Flutter的移動端跨平臺應用實踐

上圖是工程上的修改點。綠色虛線部分是 Flutter 默認的結構,紅色虛線是閑魚在 Flutter 基礎上做的定制。Flutter 的構建工具 gen_snapshot,會把業務代碼,Flutter 框架、引擎編譯成中間產物,以 so 或 Framework 的方式變成 Native 的一部分。

幾個主要的改動點:

第一,構建私有的倉庫,用來管理阿里私有包,如 CDN、無線網關等中間件適配 Package。

第二,構建工具和引擎的優化。

第三,跟現有的構建工具打通,混合調試等。

閑魚基于Flutter的移動端跨平臺應用實踐

閑魚基于Flutter的移動端跨平臺應用實踐

Flutter Hybrid 棧管理

除了上述的研發時問題,接下來就是讓它跑起來,解決運行時問題。其中最重要的是實現混合棧。

混合棧的定義

閑魚基于Flutter的移動端跨平臺應用實踐

在混合工程中,Native 頁面,Flutter 頁面之間會以多種可能的順序混合入棧,出棧。要怎么去做?先看一下 Flutter 內部棧的管理默認下是怎么做的:

閑魚基于Flutter的移動端跨平臺應用實踐

整個 Flutter 運行在一個單例的 Activity 容器里(用安卓舉例),Flutter 內部的所有頁面都在這個容器中管理。 對安卓來說,怎樣把這樣容器里面的棧與 Native 棧混合起來,直接的一個想法就要把棧自己托管起來,把這個容器在 Android 的棧中來回移動。但 Android 里想這樣操作非常難。

所以解決這個事情,就主要有兩個問題要考慮,首先就是混合棧要在哪里管理?是在 Hybrid 棧管理,還是在 Flutter 管理,第二個就是關于實例剝離的問題,既然移動單例很復雜,那就把單例剝離出來,在上面 Wrap 出多個實例,這樣就方便管理了。 下面是兩個對比方案。

閑魚基于Flutter的移動端跨平臺應用實踐

這兩種方案都是可選的,方案一就是把 Flutter 直接變成多例,每個 Flutter 頁面重新啟動一個 Flutter 的容器,每個 Flutter 頁面就像通常使用 WebView 一樣,這個方便我們做了實測,發現它的啟動速度有影響,能感覺到一些卡頓,另外,還有一個問題,當我想在兩個頁面之間去復用數據的時候,那兩個引擎之間是完全隔離的,最后數據不好復用。 這個方案的好處是很簡單,如果喜歡隔離性,也可以變成優點。

第二種方案,就是做淺層的單例剝離,盡量多的遵守 Flutter 的標準運行方式,以最小的影響把單例剝離出來,Wrap 成多例。

這種方案是在 Flutter View 這一層剝離,關于 Flutter View 的概念看一下源碼很容易理解。

這種解決方案的好處是可以實現多頁面復用,因為不用每次都取一個新的實例,加載速度會更快,因為對閑魚來講,我們追求的就是性能,最后我們的選擇就是方案二。

這個是具體的實現方式:

閑魚基于Flutter的移動端跨平臺應用實踐

把下面的 View 復用,在多個 Activity 之間移動,切換到下一個頁面的時候,把這個可復用的 View 從前一個 Activity 移走,放到下一個 Activity,這是它的主要的思路。

在這個思路下也會遇到一些需要解決的問題:

  • 兩個頁面轉場動畫由于 View 在 Activity 間移動,會有一個短暫的白色閃屏,體驗不好,解決閃屏的辦法,就是做一個截圖,從 A 頁面到 B 頁面的時候,對 A 頁面做個截圖,同時把 Flutter 自帶棧的轉場動畫禁止掉,有這個截圖,轉場時就不會有閃屏的感覺了。
  • 考慮對統一 OpenUL 支持,把 Flutter 和 Native 的 URL 統一。
  • 由于 Flutter 容器內部有個棧管理,對這個棧需要與 Native 做同步的跟隨。

到此,混合棧的方案就簡單介紹完了。

基于 Texture 的自定義視頻播放器

接下來,如果 Flutter 頁面中想復用已有的 Native 組件,怎么辦?

一種情況是視頻播放器,Native 中我們做過很多優化的播放器,希望能復用到 Flutter 頁面中。

首先,還是先看原理:

閑魚基于Flutter的移動端跨平臺應用實踐 Flutter 內部的渲染,與通常的做法一樣,有 layer。其中一種 Layer 叫 Texture Layer,可以把任何其他地方計算出來的紋理直接貼到 Flutter 的 Texture Layer 上。不管是視頻,還是圖片,如果有需要,都可以用 Texture Layer。

閑魚基于Flutter的移動端跨平臺應用實踐

在這個實現的方式中,Flutter 側負責展示這個播放器 UI,接收對播放器做控制交互,而 Native 側負責視頻的渲染,通過 TextureLayer 展示到 Flutter 側。而控制協議,通過 Flutter 特有的 MethodChannel 來控制。

除了視頻,還有沒有其他類型的 Native 組件能復用到 Flutter 中?像下圖這樣,把 Native 控件放在 View/Window 中與 Flutter 混合,是可以的。但截止演講時,Flutter 還無法做到在 Flutter 中挖個小天窗嵌入 Native 組件。不過這個方式 Google Flutter 團隊已經在做嘗試,未來可能做有辦法支持,大家可以關注。

閑魚基于Flutter的移動端跨平臺應用實踐

Flutter 通用問題實踐

接下來,介紹一下 Flutter 商品詳情頁的頁面的開發框架。

頁面框架

閑魚基于Flutter的移動端跨平臺應用實踐

右邊邊綠色的這一部分,就是整個頁面的結構,整個詳情頁面是一個大列表,由商品的描述、圖片,評論,個性化推薦等組成。這里簡單概括幾個特點:

  • 通過 Server 端返回的數據驅動 UI 界面,可以一定程度上獲得頁面內容的動態能力。Flutter 本身不支持動態更新,無法像 JS 那樣,所以這種設計方式可以一定程度上彌補這方面的短板。
  • Widget 樹結點間(或者說頁面的不同組件間)的數據如何共享?這里大家知道 InheritedWidget 這個類就好了,這是解決數據共享的很有用的類。
  • 如果頁面再復雜些,有很多交互,希望將視頻、交互、數據等分離怎么辦?也可以考慮引入 Redux 框架。

統一協議

閑魚基于Flutter的移動端跨平臺應用實踐

Flutter 不支持 Dart 的反射(mirror),所以在開發 Flutter 頁面時,解析服務端返回的數據,生成 Flutter 對象時,可能會很不習慣,需要有較多的硬編碼。 Flutter 不支持反射,請大家理解,這樣可以獲得 tree shaking 能力,減少 Flutter 包的大小。

既然不支持反射,怎么去解決剛才說的數據轉換問題?我們實現了一個統一協議層,把 Serve 端和客戶端的請求接口和數據模型,都通過協議統一生成代碼,避免了手工編碼。

圖片緩存方案

閑魚基于Flutter的移動端跨平臺應用實踐

閑魚的頁面中有大量圖片,但 Flutter 默認的圖片緩存策略比較簡單,截止演講時,如上圖所示,默認圖片緩存策略是按照圖片數量,以 1000 為上限,LRU 的方式置換。當大圖片較多時,這會占用過多的內存,容易造成 Crash 或 Abort。

閑魚基于Flutter的移動端跨平臺應用實踐

在我們只有詳情頁一種頁面時,解決這個問題可以用簡單粗暴的方式,首先把 1000 這個數量調小。一種修改方式如圖所示,通過 WidgetsFlutterBinding 來修改(WidgetsFlutterBinding 是 Flutter 中很重要的一個機制,有興趣可以深入了解)。

此外,還要注意圖片尺寸自適應剪裁,支持 WebP 等,這些對節省圖片內存和網絡流量都很關鍵。

第二種解決方案,是官方正在做的優化,按照整個空間的大小來做緩存策略,具體可以關注圖中的鏈接。

第三種方案,更加完善,加一層持久層的緩存,以實際的經驗來看,閑魚的場景下,持久層緩存時,通常可以提高緩存命中率 10% 到 30% 。

上線效果

線上 Crash 率

閑魚基于Flutter的移動端跨平臺應用實踐

大家可能會關心 Flutter 在生產環境的穩定性,兼容性等表現。閑魚使用 Flutter 的前期階段,這方面確實有很大的問題。前期在真實環境中發現了很多問題,第一次灰度測試時 Crash 率有百分之一的量級,主要的 Crash 問題包括內存、GPU、icu data、視頻播放、截圖接口、armv7、字體缺失等。

我們和 Google 團隊一起,通過幾個版本的灰度迭代,用了一個半月的時間,把問題逐步解決了,目前 Crash 率收斂穩定,達到萬分之一的量級,已經達到了生產標準。

Flutter 與 Native 詳情頁性能對比

我們對 Flutter 與 Native 的詳情頁做了簡單的性能對比,并不嚴謹,僅供參考。

測試場景:進入寶貝詳情頁后快速瀏覽到頁面底部,從猜你喜歡進入第二個寶貝,重復進行訪問 10 個不同寶貝詳情。對比 Native 版詳情頁和 Flutter 版詳情頁。

測試機型,以低端機型為主(高端機型區分不明顯):

Android 4.x, 5.x...

iPhone 5c, 6s...

安卓的對比:

閑魚基于Flutter的移動端跨平臺應用實踐

下面兩行是體現流暢度的,LPS 或者 MS 是騰訊提出的一種流暢度的表達方式,在流暢度是 OK 的,比 Native 詳情頁做的好,在技術的指標上也還不錯。

iOS 的結果:

閑魚基于Flutter的移動端跨平臺應用實踐

iOS 上,也是 Flutter 會更流暢一些,測下來,發現在 GPU 的使用率上,Flutter 會更高一些,Flutter 在這上有更進一步的優化空間。

說到這里,可能大家也會疑惑,這個對比結果,是不是因為以前 Native 寫的詳情頁太復雜了? 確實有這種可能。但主要分享的是,兩種頁面是相同團隊成員開發的,并且沒有針對 Flutter 做專門的性能優化,這個性能測試可以確定的結論是,使用 Flutter 還是比較容易就能開發出與 Native 性能相近的頁面。

最后,說一下大家可能會關于的成本問題。對于混合開發,初期接入成本是有的。如果是全新的 Flutter 獨立應用,接入成本會很低。首次接入完成后,后面開始會順利很多,可以享受跨端統一編程,一套代碼帶來的效率快感。另外,關于學習成本,還好,因為 Dart 語言跟 Java 很像,跟 JS 也很像,另外 Flutter 的 UI 框架遵循響應式,聲明式設計原則,個人感覺,較容易上手。謝謝大家,由于水平有限,可能會有錯誤,請大家指正。篇幅有限本文無法對每個細節深入探討,關于細節的深入分享,歡迎大家關注“閑魚技術”的公眾號。

作者簡介

王樹彬,阿里巴巴閑魚無線技術專家,畢業于浙江大學,2009 年加入阿里巴巴,現任阿里巴巴閑魚架構負責人,負責閑魚從端到云的整體架構升級。有十余年互聯網研發經驗。曾負責移動端 LBS 技術,是淘寶位置歸一、地理圍欄等技術的開拓者,為個性化、O2O 等業務提供基礎能力。也曾負責淘寶的商家系統,建立商家十億級大數據下的實時在線查詢、挖掘服務。

感謝覃云對本文的審校。

 

來自:http://www.infoq.com/cn/articles/xianyu-cross-platform-based-on-flutter

 

 本文由用戶 IsobelCovin 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!