每日最新頭條.有趣資訊

使用Flutter之後,我們的CPU佔用率降了50%

作者 | 王敏君(華泰證券前端技術團隊)

編輯 | 覃雲

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

1

漲樂 Flutter 實踐(以 iOS 為例)

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

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

2

組件化集成

如何將 Flutter 代碼集成進現有工程是我們遇到的第一個挑戰。Flutter 官網 (https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps#ios) 提供了一種解決方案,但存在以下缺陷:

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

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

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

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

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

Flutter.framework:Flutter 的 SDK。

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 即可。

3

混合棧管理

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

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

盡量減少資源消耗;

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

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

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

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

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

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

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

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

自動化

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4

降級策略

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

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

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

5

實踐結果

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 頁面。

6

總結

從我們的實踐結果來看,Flutter 在性能方面擁有絕對優秀的體驗,但 Flutter 的開發生態還不夠成熟,完全取代原生開發實現跨平台為時尚早,但對於一些追求一致性、高性能的界面可以嘗試採用 Flutter 實現。我們漲樂財富通開發團隊也會持續跟進 Flutter 的發展,將 Flutter 推廣應用到更多業務場景中。

點個好看少個 bug

獲得更多的PTT最新消息
按讚加入粉絲團