使用 Flutter 之後,我们的 CPU 占用率降了 50%

近年來,移動互聯網迅猛發展,業務需求頻繁更新,業務內容動態化需求急劇增加,純原生開發已經無法滿足業務快速增長的需求,因此誕生了多種跨平台開發框架,如 H5+ 原生開發、React Native 和 Weex ,但這兩年最受開發者青睞的莫過於 Flutter。目前,很多應用都集成了 Flutter,團隊也實現了完整 Flutter 的集成過程,以下篇幅會具體介紹整個開發過程。

Flutter 實踐(以 iOS 為例)

此次實踐主要是為了驗證整個流程,為後續大規模應用 Flutter 做鋪墊,因此我們選擇了一個業務相對簡單的「技術論市」頁面進行改造,該頁面之前是 H5 實現的列表頁,點擊欄目會跳轉到另一個 H5 頁面詳情頁。

改造之後,原生介面點擊按鈕會打開 Flutter 列表頁,點擊 Flutter 頁面的欄目會跳轉到 H5 頁面,點擊返回可依次返回到上一個介面。從圖中可以看出,整個流程的使用體驗非常流暢。

組件化集成

如何將 Flutter 代碼集成進現有工程是我們遇到的第一個挑戰。Flutter 官網提供了一種解決方案,但存在以下缺陷:

1. 需要修改主工程的配置,入侵原有工程;

2. 工程運行需要 Flutter 環境,而在實際開發中並不是所有的團隊成員都會參與到 Flutter 的開發,安裝 Flutter 環境對於那些不需要 Flutter 開發的成員來說顯然不合理。

官網的方案行不同,我們必須另闢蹊徑。研究 Flutter 的編譯腳本xcode_backend.sh發現,只要將 Flutter 編譯產物放入主工程就能運行 Flutter 模塊。為了便於管理採用私有 pod 管理編譯產物的方式來集成 Flutter。

Flutter 工程的編譯產物包含三個部分,分別是:

1.App.framework:所有的 Dart 代碼,包括業務代碼和依賴的第三方 package 代碼,在 Debug 模式下只是一個空殼,在 Release 模式下是所有代碼生成的機器碼。

2.Flutter.framework:Flutter 的 SDK。

3.flutter_assets:Flutter 資源文件,包括字體、圖片等。

創建私有 pod 用來管理這些編譯產物,podspec 的核心內容如下:

複製代碼

s.source_files = 'htflutter_product_debug/Plugin/**/*' s.vendored_frameworks = 'Framework/*.framework', 'Framework/engine/*.framework' s.resources = 'Framework/flutter_assets'主工程需要集成 Flutter 模塊時,只需要在 podfile 中依賴該私有 pod 即可。

混合棧管理

引入 Flutter 模塊后,需要考慮的就是如何管理混合棧。在現有應用中,已經存在原生 + 網頁的混合棧,如今引入了 Flutter 需要解決這三者如何嵌套。
混合棧管理的方案必須具備以下特點:

1. 原生、H5、Flutter 頁面三者能相互調用,並且用戶感覺不到差異;

2. 盡量減少資源消耗;

3. 每個頁面的生命周期保持完整。

為此,我們借鑒了 Alibaba 閒魚團隊開源的混合 Stack 管理方案,並與我們現有的路由管理方案相結合實現了混合棧管理,具體架構圖如下:

  • 頁面跳轉使用統一的路由管理。

使用路由管理器來統一管理頁面。當需要打開一個 Flutter 頁面時,只需要像原來一樣,發送一個打開 Flutter 的路由,並攜帶參數用來標識具體的頁面。路由管理器識別到是 Flutter 路由後會創建新的WrapFlutterViewController並壓棧。WrapFlutterViewController會使用 FlutterViewController單例作為其子 VC,利用傳遞過來在參數在FlutterViewController內部打開具體的 Flutter 頁面。

  • 所有的 Flutter 頁面共用一個 Flutter 實例,iOS 使用 FlutterViewController,Android 使用 FlutterNativeView

共用一個 Flutter 實例,既可以使得 Flutter 頁面之間實現數據通信和共享,也可以減少額外的資源消耗。因為每一個 Flutter 實例會啟動三個線程,分別是 UI 線程、GPU 線程和 IO 線程,只創建一個 Flutter 實例減少了資源的使用。

  • 每一個 Flutter 頁面對應一個原生頁面。

每次 push/pop 一個 Flutter 頁面,一方面會操作 Flutter 實例內部的導航棧,另一方面在外部會 push/pop 一個原生的頁面,這樣可以確保 Flutter 頁面和原生頁面的同步。

自動化

整個 Flutter 的開發過程分為以下兩大步驟:

1. 編寫 dart 和 plugin 代碼並生成 App.framework,Flutter_Asset 文件夾和 Flutter.framework;

2. 將編譯產物集成到 iOS 主工程;

自動化需要解決幾個關鍵問題:

1. 如何區分 debug 和 release 模式下的產物包

2. 自動化的流程應該如何控制

針對第一個問題,我們的解決辦法是創建兩個 repo,htflutter_product_debughtflutter_product_release,開發使用 debug 產物,生產使用 release 產物。

第二個問題,我們參考的是 CocoaPods 的 pod 發布流程,將 Flutter 主工程作為一個私有 repo 來看待,通過 tag 觸髮腳本生成產物,再 push 到htflutter_product_debughtflutter_product_release。具體流程如下圖:

1. 首先htcftflutter是我們的 Flutter 主工程,包含所有的 Dart 源代碼和 plugin 代碼。

2. 通過 tag 名觸髮腳本,編譯出兩種模式的產物,例如 tag:debug_1.0則編譯出 debug prudoct。

3. 將產物推送到遠端產物 pod repo(這一步實際上類似 pod repo push)。這一步相對複雜一點,

首先需要 clone 遠端的產物 pod 到當前的某個臨時文件夾,然後將 Flutter 主工程中編譯的產物拷貝到臨時文件夾中,其中包含 App.framework,flutter_assets 文件夾以及 Flutter.framework,另外還有 plugin 相關文件。前面三個都好辦,直接拷貝即可,plugin 比較麻煩,plugin 的代碼通過 package 的形式引入到工程中,並不在 Flutter 主工程,需要從.flutter-plugins文件中讀取到各個 plugin 到路徑,然後到對應到路徑進行拷貝。拷貝完成之後,再通過腳本完成git的相關操作即可,最後 push 完成,刪除臨時文件夾,這樣htcftflutter不感知整個腳本執行過程。

4.iOS 主工程集成 Flutter 產物 pod,默認情況下podfile中依賴htflutter_product_debug, 主工程打 release tag 時,觸髮腳本將依賴修改成htflutter_product_release,並執行pod update
相關腳本如下:

複製代碼

tool = HTTool.new(mode) //debug/release tool.build_ios() # 編譯產物 tool.clone_flutter_product_repo() #clone product repo tool.copy_products() # 拷貝產物 tool.copy_plugin_code() # 拷貝 plugin 原生代碼 tool.updateSpecVersion() # 更新版本 tool.push_flutter_product_repo() #push to product repo tool.remove_product_repo()#delete product repo after push## 降級策略

Flutter 還處於快速疊代發展的階段,正式上線可能存在不確定的風險,為此我們設計了具體的降級方案,應對 Flutter 發生異常的情況。

1. 應用啟動時,伺服器會下發 Flutter 降級配置表,key 是需要降級的 Flutter 頁面路徑,value 是需要執行的降級路由操作;

2. 路由管理器響應 Flutter 路由時,會首先判斷需要打開的 Flutter 頁面是否需要降級,若需要,則會執行配置表中的路由操作,降級到網頁;反之則正常跳轉到 Flutter 頁面。

實踐結果

1. 安裝包大小

引入 Flutter 之前,財富通的安裝包為 94MB,引入之後大小為 100MB,發現增大了 6MB,這其中主要是引入了 Flutter 的 SDK,增加的大小在可以接受的範圍。

2.FPS 和 GPU

從上圖可以看出,Flutter 的 FPS 接近 60,和原生效果基本一致,而 H5 的 FPS 在 50 左右,遠不如 Flutter 優秀。兩者的 GPU 使用率基本相同。

3. 記憶體

記憶體表現方面,H5 頁面使用的記憶體要小於 Flutter。

4.CPU

從 CPU 的佔用率來看,Flutter 佔用的 CPU 要遠遠小於 H5 頁面。

總結

從實踐結果來看,Flutter 在性能方面擁有絕對優秀的體驗,但 Flutter 的開發生態還不夠成熟,完全取代原生開發實現跨平台為時尚早,但對於一些追求一致性、高性能的介面可以嘗試採用 Flutter 實現。