使用Flutter之后,我们的CPU占用率降了50%
近年來(lái),移動(dòng)互聯(lián)網(wǎng)迅猛發(fā)展,業(yè)務(wù)需求頻繁更新,業(yè)務(wù)內(nèi)容動(dòng)態(tài)化需求急劇增加,純?cè)_(kāi)發(fā)已經(jīng)無(wú)法滿足業(yè)務(wù)快速增長(zhǎng)的需求,因此誕生了多種跨平臺(tái)開(kāi)發(fā)框架,如 H5+ 原生開(kāi)發(fā)、React Native 和 Weex ,但這兩年最受開(kāi)發(fā)者青睞的莫過(guò)于 Flutter。目前,很多應(yīng)用都集成了 Flutter,我們團(tuán)隊(duì)也在漲樂(lè)財(cái)富通上實(shí)現(xiàn)了完整 Flutter 的集成過(guò)程,以下篇幅會(huì)具體介紹整個(gè)集成過(guò)程。
漲樂(lè) Flutter 實(shí)踐(以 iOS 為例)
此次實(shí)踐主要是為了驗(yàn)證整個(gè)流程,為后續(xù)大規(guī)模應(yīng)用 Flutter 做鋪墊,因此我們選擇了一個(gè)業(yè)務(wù)相對(duì)簡(jiǎn)單的“技術(shù)論市”頁(yè)面進(jìn)行改造,該頁(yè)面之前是H5實(shí)現(xiàn)的列表頁(yè),點(diǎn)擊欄目會(huì)跳轉(zhuǎn)到另一個(gè)H5頁(yè)面詳情頁(yè)。
改造之后,原生界面點(diǎn)擊按鈕會(huì)打開(kāi) Flutter 列表頁(yè),點(diǎn)擊 Flutter 頁(yè)面的欄目會(huì)跳轉(zhuǎn)到 H5 頁(yè)面,點(diǎn)擊返回可依次返回到上一個(gè)界面。從圖中可以看出,整個(gè)流程的使用體驗(yàn)非常流暢。
組件化集成
如何將 Flutter 代碼集成進(jìn)現(xiàn)有工程是我們遇到的第一個(gè)挑戰(zhàn)。Flutter 官網(wǎng)提供了一種解決方案,但存在以下缺陷:
1.需要修改主工程的配置,入侵原有工程;
2.工程運(yùn)行需要 Flutter環(huán)境,而在實(shí)際開(kāi)發(fā)中并不是所有的團(tuán)隊(duì)成員都會(huì)參與到 Flutter 的開(kāi)發(fā),安裝 Flutter 環(huán)境對(duì)于那些不需要 Flutter 開(kāi)發(fā)的成員來(lái)說(shuō)顯然不合理。
官網(wǎng)的方案行不同,我們必須另辟蹊徑。研究 Flutter 的編譯腳本xcode_backend.sh發(fā)現(xiàn),只要將 Flutter 編譯產(chǎn)物放入主工程就能運(yùn)行 Flutter 模塊。為了便于管理,漲樂(lè)財(cái)富通采用私有 pod 管理編譯產(chǎn)物的方式來(lái)集成 Flutter。
Flutter 工程的編譯產(chǎn)物包含三個(gè)部分,分別是:
1.App.framework:所有的 Dart 代碼,包括業(yè)務(wù)代碼和依賴的第三方 package 代碼,在 Debug 模式下只是一個(gè)空殼,在 Release 模式下是所有代碼生成的機(jī)器碼。
2.Flutter.framework:Flutter 的 SDK。
3.flutter_assets:Flutter 資源文件,包括字體、圖片等。
創(chuàng)建私有 pod 用來(lái)管理這些編譯產(chǎn)物,podspec 的核心內(nèi)容如下:
s.source_files = 'htflutter_product_debug/Plugin/**/*' s.vendored_frameworks = 'Framework/*.framework', 'Framework/engine/*.framework' s.resources = 'Framework/flutter_assets'主工程需要集成 Flutter 模塊時(shí),只需要在 podfile 中依賴該私有 pod 即可。
混合棧管理
引入 Flutter 模塊后,需要考慮的就是如何管理混合棧。在現(xiàn)有應(yīng)用中,已經(jīng)存在原生 + 網(wǎng)頁(yè)的混合棧,如今引入了 Flutter 需要解決這三者如何嵌套。
混合棧管理的方案必須具備以下特點(diǎn):
1.原生、H5、Flutter 頁(yè)面三者能相互調(diào)用,并且用戶感覺(jué)不到差異;
2.盡量減少資源消耗;
3.每個(gè)頁(yè)面的生命周期保持完整。
為此,我們借鑒了閑魚團(tuán)隊(duì)開(kāi)源的混合棧管理方案,并與我們現(xiàn)有的路由管理方案相結(jié)合,在漲樂(lè)上實(shí)現(xiàn)了混合棧管理,具體架構(gòu)圖如下:
- 頁(yè)面跳轉(zhuǎn)使用統(tǒng)一的路由管理。
漲樂(lè)財(cái)富通使用路由管理器來(lái)統(tǒng)一管理頁(yè)面。當(dāng)需要打開(kāi)一個(gè) Flutter 頁(yè)面時(shí),只需要像原來(lái)一樣,發(fā)送一個(gè)打開(kāi) Flutter 的路由,并攜帶參數(shù)用來(lái)標(biāo)識(shí)具體的頁(yè)面。路由管理器識(shí)別到是 Flutter 路由后會(huì)創(chuàng)建新的WrapFlutterViewController并壓棧。WrapFlutterViewController會(huì)使用 FlutterViewController單例作為其子VC,利用傳遞過(guò)來(lái)在參數(shù)在FlutterViewController內(nèi)部打開(kāi)具體的 Flutter 頁(yè)面。
- 所有的 Flutter 頁(yè)面共用一個(gè) Flutter 實(shí)例,iOS 使用 FlutterViewController,Android 使用 FlutterNativeView。
共用一個(gè) Flutter 實(shí)例,既可以使得 Flutter 頁(yè)面之間實(shí)現(xiàn)數(shù)據(jù)通信和共享,也可以減少額外的資源消耗。因?yàn)槊恳粋€(gè) Flutter 實(shí)例會(huì)啟動(dòng)三個(gè)線程,分別是 UI 線程、GPU 線程和 IO 線程,只創(chuàng)建一個(gè) Flutter 實(shí)例減少了資源的使用。
- 每一個(gè) Flutter 頁(yè)面對(duì)應(yīng)一個(gè)原生頁(yè)面。
每次 push/pop 一個(gè) Flutter 頁(yè)面,一方面會(huì)操作 Flutter 實(shí)例內(nèi)部的導(dǎo)航棧,另一方面在外部會(huì) push/pop 一個(gè)原生的頁(yè)面,這樣可以確保 Flutter 頁(yè)面和原生頁(yè)面的同步。
自動(dòng)化
整個(gè) Flutter 的開(kāi)發(fā)過(guò)程分為以下兩大步驟:
1.編寫dart和plugin代碼并生成 App.framework,Flutter_Asset 文件夾和 Flutter.framework;
2.將編譯產(chǎn)物集成到iOS主工程;
自動(dòng)化需要解決幾個(gè)關(guān)鍵問(wèn)題:
1.如何區(qū)分 debug 和 release 模式下的產(chǎn)物包
2.自動(dòng)化的流程應(yīng)該如何控制
針對(duì)第一個(gè)問(wèn)題,我們的解決辦法是創(chuàng)建兩個(gè)repo,htflutter_product_debug和htflutter_product_release,開(kāi)發(fā)使用 debug 產(chǎn)物,生產(chǎn)使用 release 產(chǎn)物。
第二個(gè)問(wèn)題,我們參考的是 CocoaPods 的 pod 發(fā)布流程,將 Flutter 主工程作為一個(gè)私有 repo 來(lái)看待,通過(guò) tag 觸發(fā)腳本生成產(chǎn)物,再 push 到htflutter_product_debug和htflutter_product_release。具體流程如下圖:
1.首先htcftflutter是我們的 Flutter 主工程,包含所有的 Dart 源代碼和 plugin 代碼。
2.通過(guò) tag 名觸發(fā)腳本,編譯出兩種模式的產(chǎn)物,例如 tag:debug_1.0則編譯出 debug prudoct。
3.將產(chǎn)物推送到遠(yuǎn)端產(chǎn)物 pod repo(這一步實(shí)際上類似pod repo push)。這一步相對(duì)復(fù)雜一點(diǎn),
首先需要 clone 遠(yuǎn)端的產(chǎn)物 pod 到當(dāng)前的某個(gè)臨時(shí)文件夾,然后將 Flutter 主工程中編譯的產(chǎn)物拷貝到臨時(shí)文件夾中,其中包含 App.framework,flutter_assets 文件夾以及 Flutter.framework,另外還有 plugin 相關(guān)文件。前面三個(gè)都好辦,直接拷貝即可,plugin 比較麻煩,plugin 的代碼通過(guò) package 的形式引入到工程中,并不在 Flutter 主工程,需要從.flutter-plugins文件中讀取到各個(gè) plugin 到路徑,然后到對(duì)應(yīng)到路徑進(jìn)行拷貝。拷貝完成之后,再通過(guò)腳本完成git的相關(guān)操作即可,最后push完成,刪除臨時(shí)文件夾,這樣htcftflutter不感知整個(gè)腳本執(zhí)行過(guò)程。
4.iOS 主工程集成 Flutter 產(chǎn)物 pod,默認(rèn)情況下podfile中依賴htflutter_product_debug,主工程打 release tag 時(shí),觸發(fā)腳本將依賴修改成htflutter_product_release,并執(zhí)行pod update。
相關(guān)腳本如下:
降級(jí)策略
Flutter 還處于快速迭代發(fā)展的階段,正式上線可能存在不確定的風(fēng)險(xiǎn),為此我們?cè)O(shè)計(jì)了具體的降級(jí)方案,應(yīng)對(duì) Flutter 發(fā)生異常的情況。
1.應(yīng)用啟動(dòng)時(shí),服務(wù)器會(huì)下發(fā) Flutter 降級(jí)配置表,key 是需要降級(jí)的 Flutter 頁(yè)面路徑,value 是需要執(zhí)行的降級(jí)路由操作;
2.路由管理器響應(yīng) Flutter 路由時(shí),會(huì)首先判斷需要打開(kāi)的 Flutter 頁(yè)面是否需要降級(jí),若需要,則會(huì)執(zhí)行配置表中的路由操作,降級(jí)到網(wǎng)頁(yè);反之則正常跳轉(zhuǎn)到 Flutter 頁(yè)面。
實(shí)踐結(jié)果
1.安裝包大小
引入 Flutter 之前,漲樂(lè)財(cái)富通的安裝包為94MB,引入之后大小為100MB,發(fā)現(xiàn)增大了6MB,這其中主要是引入了 Flutter 的 SDK,增加的大小在可以接受的范圍。
2.FPS 和 GPU
從上圖可以看出,Flutter 的 FPS接近60,和原生效果基本一致,而 H5 的 FPS 在50左右,遠(yuǎn)不如 Flutter 優(yōu)秀。兩者的 GPU 使用率基本相同。
3.內(nèi)存
內(nèi)存表現(xiàn)方面,H5 頁(yè)面使用的內(nèi)存要小于 Flutter。
4.CPU
從 CPU 的占用率來(lái)看,Flutter 占用的 CPU 要遠(yuǎn)遠(yuǎn)小于 H5 頁(yè)面。
總結(jié)
從我們的實(shí)踐結(jié)果來(lái)看,Flutter 在性能方面擁有絕對(duì)優(yōu)秀的體驗(yàn),但 Flutter 的開(kāi)發(fā)生態(tài)還不夠成熟,完全取代原生開(kāi)發(fā)實(shí)現(xiàn)跨平臺(tái)為時(shí)尚早,但對(duì)于一些追求一致性、高性能的界面可以嘗試采用 Flutter 實(shí)現(xiàn)。我們漲樂(lè)財(cái)富通開(kāi)發(fā)團(tuán)隊(duì)也會(huì)持續(xù)跟進(jìn) Flutter 的發(fā)展,將 Flutter 推廣應(yīng)用到更多業(yè)務(wù)場(chǎng)景中。
更多內(nèi)容,請(qǐng)關(guān)注前端之巔。
總結(jié)
以上是生活随笔為你收集整理的使用Flutter之后,我们的CPU占用率降了50%的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: XMLHttpRequest
- 下一篇: [原] 探索 EventEmitter