FlutterBoost1.0到2.0,我一共做了這幾件事...

2022-01-05 閒魚技術

收錄於話題 #閒魚Flutter 87個

背景

一定規模的App開發如要引入Flutter開發體系,因某些原因如底層二、三方Native庫或頁面調用,不可避免需要混合開發的能力,但Flutter本身是個單容器的應用,純粹引入SDK會遇到頁面在Flutter和Native跳轉無法流暢切換,沒有統一的路由管理等問題。我們發布的FlutterBoost1.0能很好的解決這些問題(文檔參考這裡)。同時,我們也持續關注到以下痛點:

Flutter在不斷的演進升級,其演進會給上層應用來到更多可能;

同時,在我們的業務應用中,FlutterBoost1.0在線上使用的過程中遇到一些如黑屏和白屏的反饋,這些歷史遺留問題我們希望解決掉;

最後,社區的關注及需求是推動我們前進的動力,我們也希望藉此將FlutterBoost的開源做的更完善,比如增加更多測試用例,更多文檔等等。

這篇文章介紹了FlutterBoost2.0(統稱1.0之後的所有適配版本,下同)針對上述問題的架構升級,並且重點介紹iOS端在升級的過程中遇到的問題和解決方式。

問題分析

背景容器的升級:渲染和引擎的解耦

大家知道FlutterBoost1.0是單頁面模式,即不管你打開多少Flutter頁面,其實呈現頁面的FlutterViewController或者FlutterView其實僅有一個,這其實是有歷史原因的。讓我們從Flutter底層的架構說起。Flutter發展到現在,在Plugin, ViewController(FlutterView),Flutter Engine這三個核心對象的管理上一直在演進。1.5版本是個分水嶺,終於對這三個對象做了較好的解耦。如flutter0.x版本,這三個對象的關係及我們使用API的方式是這樣的(根本看不到Engine對象):

作為使用方,我們看不到Engine對象,因為Engine已經內置在FlutterViewController中,沒有暴露出來。Flutter1.0做了解耦和改進,如下圖:

我們可以看到雖然全局還是僅有一個FlutterViewController實例,但FlutterEngine被單獨剝離出來。不過三者關係還是沒理清。如,Plugin似乎應該註冊到Engine上更合理,怎麼會和負責渲染的FlutterViewController發生關係;Flutter Engine雖然已經暴露出來給用戶直接使用,但和VC之間還是1對1的關係,按理說負責引擎和數據的Engine全局唯一,渲染層FlutterView應該可以多次切換啊。終於在1.5之後,我們看到Flutter團隊努力的結果:

Engine終於完全剝離,Plugin也終於「嫁」對了人。FlutterViewController和Engine不再是同生共死的組合關係,而僅是個通過VC表現層呈現及事件獲取的依賴。我們可以猜到Flutter嘗試往多引擎方向發展的努力!相應的,我們也給FlutterBoost提出了適配Flutter新架構的要求。

頁面白屏或黑屏

FlutterBoost1.0受限於Flutter的架構,出於節省內存的考慮,全局只有一個FlutterViewController。同時在混合頁面滑動切換的時候,為了快速顯示上一個頁面並實現原生頁面切換的效果,採用CPU截圖的方式為每個頁面保存了打底圖。打底圖通過文件和內存二級緩存的方式避免持有多張圖片的內存問題。但這也帶來了一些問題:在切換的過程中因需要對老頁面截圖及加載之前截圖圖片等耗時工作,會偶爾出現白屏或者黑屏問題——截圖和加載都在CPU線程上進行,會影響主線程渲染;而且在頁面切換的時候,截圖和加載圖片操作雖然處於降低內存的目的,但會帶來短暫的內存飆漲(見下圖),雖然持續的時間很短,但帶來了OOM的abort的風險。

生命周期管理

Flutter目前的架構是單實例的,這意味著一個FlutterViewController的生命周期和整個App的生命周期AppLifecycleState是一致的。頁面完全隱藏或者app切後臺都會發送pause消息告知監聽者告知app要暫停。但這在有多個flutter頁面和原生頁面共存的混合棧情形下顯然不合理。針對單個Flutter Container頁面,也需要有自己可見與不可見的事件通知。FlutterBoost1.0中沒有解決這個問題,在閒魚內部也導致一些小問題,比如二次打開視頻播放頁面後,老頁面通過app在WidgetBinding中監聽了pause事件就將播放器stop,但同時也將新頁面本自動播放的視頻也停止了。如果有單獨的頁面事件來分別精準控制而非依賴於整個App的事件就能解決這個問題。我們曾就這個VC頁面和應用生命周期的設計和Flutter的同學討論過,他們也覺得是個問題,但受限於當初的設計,暫沒有具體的解決時間點。

開源建設

升級之前,我們在github上的issue較多,同時文檔不足,升級計劃也不明確,單元測試並不全面。這些短板需要我們在升級後解決。

解決方案

正是基於上述的問題,我們對FlutterBoost做了升級,主要有以下幾個方面。

頁面管理方案升級

新版不再維護單一的FlutterViewController(或FlutterView),而是和原生一樣每次有新頁面請求時就直接打開新的ViewController或者FlutterView,和管理原生的頁面一毛一樣。如此,自然也不需要實現截圖功能,小夥伴們再也不用擔心通過CPU截圖導致白屏或者黑屏的困擾啦。我們看如下頁面結構前後的對比。如上圖,升級前全局只有一個FlutterViewAdapter(其實是FlutterViewController),負責FlutterView渲染子View並將其內嵌在每個FLBFlutterViewContainer(繼承自UIViewController)中,每次新的FLBFlutterViewContainer拉起時就需要複雜的detach和attach操作來轉移唯一的FlutterView,同時進行截圖緩存。升級後,不再需要內嵌的FlutterViewAdapter和Screenshot緩存列表:其底層實現也變的更加簡單,不再需要在detach頁面的時候截圖,下圖是前後兩個方案在頁面pop和push過程中的對比。

內存及穩定性治理

在頁面管理方案升級之後,白屏和黑屏問題解決了,世界就應該安靜了。但我們繼續做了深入的內存及穩定性治理,發現新版本在iOS下每個新的VC打開的時候雖然沒有了內存飆漲的peak,但每個新頁面會帶來約10M內存的增量。拿FlutterBoost的官方demo做了測試,1.54這個版本就是升級後的原始版本:這個內存增量是因為什麼導致的?我們升級前後內存變化做了分析,如下圖(左邊是升級前,右邊是升級後):

發現內存的增量主要來自於Anonymous VM和IOSSurface。Android版本這類問題並不明顯,暫略不表。

IOSurface的增量

什麼是IOSurface?從apple的文檔裡了解到:

The IOSurface framework provides a framebuffer object suitable for sharing across process boundaries.

記得哪一年蘋果的開發者大會上重點提過這個,主要是和iOS上OpenGL的GPU內存和CPU內存管理創新有關,如CVOpenGLESTextureCacheCreateTextureFromImage就是基於IOSurface的能力直接從圖像映射為紋理,而不像OpenGL官方的glTexImage2D需要將圖像從CPU傳輸到GPU再映射,從而提高了性能。

在Flutter裡,Rasterrizer的setup和teardown會使底層系統創建和銷毀IOSurface。FlutterViewController的surfaceUpdated會用於刪除或創建Rasterrizer。我們review了升級後對於VC的surface的控制,的確有許多不合理之處——多次重複調用surfaceUpdated。

導致多次調用的根本原因脫離不了FlutterViewController單引擎單頁面的設計。FlutterVC設計並不考慮混合棧的情形,它設想的場景是全局一個engine,只需管理其唯一持有的FlutterVC的生命周期。如此,其surfaceUpdated函數固化在view的appear和disappear事件中,並沒有暴露出來。這導致混合棧在處理多FlutterVC頁面切換的時候,無法重寫頁面事件處理函數而靈活處理何時應該創建和銷毀surface,最終不可避免的重複創建surface或者銷毀surface。

同時,我們發現頁面present和push在iOS13下將被覆蓋的頁面的生命周期和新彈出的頁面生命周期順序還不一樣。比如present在新頁面view appear之後才會disppear老頁面,並不像push的時候先disppear老頁面然後再appear新頁面。這也給我們處理混合棧頁面的surfaceUpdate帶來了困難。

為了兼容這個頁面邏輯,並且儘量避免多次創建或銷毀surface,我們重新梳理了頁面的生命周期,只在viewDidAppear的時候重新創建surface,而在viewDisappear的時候僅對非前臺VC進行刪除surface。但儘管如此,受限於FlutterVC固化了surfaceUpdate的調用,這裡還是難以避免會多重複一次創建和銷毀surface。不過,內存略有改觀,線上穩定性得到不少提升。見下圖內存比較。

VM的內存增長從內存分析的Anonymous VM上看不出VM的內存增長的原因,但從升級後有多個VC這個角度看,有許多可解釋這個增長的地方。

每個新的VC打開後,其會通過CAGLLayer渲染內容,CAGLLayer會持有後臺的content。正是基於這個content,iOS系統內部對VC進行截圖,在頁面切換的時候才有半開半閉的動態效果。相對於FlutterBoost1.0的CPU截圖,系統截圖自然有許多性能的優化,但帶來內存的增長難以避免。為了驗證這個問題,我寫了個類似Flutter的用OpenGL渲染UIView的demo。果然,每次打開新頁面,內存肯定增長10M左右。這個增量似乎難以優化,只能設法避免。目前我們推薦兩種方式:

通過頁面棧裡頁面個數限制來避免過多頁面導致OOM。如閒魚這邊限制了頁面多次push後,僅保留最近3個頁面。

避免從Flutter頁面打開Flutter頁面就新建FlutterVC,可重用上次的FlutterVC。FlutterBoost提供了這種能力,BoostContainer繼承了Navigator,原生支持Navigator的能力。但這裡需要用戶自己判斷何時應該使用Navigator的push,何時用FlutterBoost的open來打開頁面。後期我們會增加一些這樣的demo。

穩定性治理

每次底層庫的升級都會帶來穩定性問題。為了保障穩定性,我們通過收集線上crash日誌的方式,定位到幾個Engine層面的bug。這些bug或提交了issue給google,或在我們engine內部版本做了規避。如無障礙模式下FlutterSemanticObject洩漏導致crash,參考https://github.com/flutter/engine/pull/14155。在Flutter1.12下,多FlutterVC情形會觸發surface空指針調用而crash,參考https://github.com/flutter/flutter/issues/52455。其他還有一些bug,我們在內部版本做了規避,並和google做了溝通,但因復現難度等原因,還未取得一致的結論。整體上,FlutterBoost2.0在閒魚內部場景升級後,經多輪灰度及線上驗證,穩定性不錯,效果較好。

頁面生命周期管理及其他

FlutterBoost完善了之前的ContainerLifeCycle,在Dart層能較好的支持頁面的appear和disappear事件,同時能監聽app切到Background或者Foreground事件。如果涉及到頁面的生命周期管理,建議您使用FlutterBoost.singleton.addBoostContainerLifeCycleObserver()來註冊相關事件監聽程序。社區同學也給了不少建議,比如幫忙優化了hero動畫的能力等。其他功能提升及使用上的建議。為了整個框架更穩定和易於回歸驗證,我們也補了一些單元測試。目前主要是Dart層面的用例(覆蓋率達70%左右),後續會增加混合頁面跳轉方面的用例。同時定義了升級計劃和release清單(見首頁)。在這輪升級後,我們總結了目前FlutterBoost的能力對比表,供參考:

總結

綜上所述,經過此次升級,flutterboost解決掉了頁面切換時白屏或者閃爍等問題,同時代碼也更簡潔易懂。同時我們對內存及穩定性做了分析。對於內存上的問題,給出了規避的方式,穩定性上我們主要通過頁面的detach和attach時序優化來解決。後續我們會繼續優化代碼,增加更多的測試用例,尤其是支持混合測試的用例。同時在Flutter頁面跳Flutter頁面上也在考慮不影響上層業務的基礎上支持一致的API。路漫漫其修遠兮,FlutterBoost會一直與時俱進,進化為更加完美的框架。也歡迎大家多參與到這個框架的開發中,一起討論並改進他。最後,我們也開發了一個使用flutterboost的腳手架:flutter-boot。歡迎使用並送小星星,地址在:https://github.com/alibaba-flutter/flutter-boot

抖音:@閒魚技術

今日頭條:@閒魚技術

● 掃碼關注我們

相關焦點

  • 探索Flutter混合開發技術方案(下)——淺析Flutter Boost原理
    很顯然,頁面的加載與Activity的生命周期相關,從Activity的onCreate開始,看看在BoostFlutterActivity整個加載過程中都做了哪些工作。onCreate:在該方法中創建了FlutterActivityAndFragmentDelegate,最終調用到Delegate的onCreateView方法創建FlutterView。
  • Flutter 開發從 0 到 1(六)Markdown 與代碼高亮
    《Fluuter 開發從 0 到 1》博客詳情是 Markdown,今天就來說說 Flutter Markdown 如何實現的。準備將 flutter_markdown 添加到 pubspec.yaml 文件中:dependencies:  flutter_markdown: ^0.4.4項目根目錄執行如下命令安裝 flutter_markdown
  • 已開源|碼上用它開始Flutter混合開發——FlutterBoost
    如果你啟動多個引擎實例,注意此時Dart VM依然是共享的,只是不同Engine實例加載的代碼跑在各自獨立的Isolate。引擎深度共享在混合方案方面,我們跟Google討論了可能的一些方案。在邏輯上這並沒有什麼壞處,但是引擎底層其實是維護了圖片緩存等比較消耗內存的對象。想像一下,每個引擎都維護自己一份圖片緩存,內存壓力將會非常大。插件註冊的問題。插件依賴Messenger去傳遞消息,而目前Messenger是由FlutterViewController(Activity)去實現的。
  • Flutter 完整開發實戰詳解 (Flutter 畫面渲染的全面解析) | 開發者說·DTalk
    我們知道當 RenderObject 的 isRepaintBoundary 為 ture 時,Flutter Framework 就會自動創建一個 OffsetLayer 來 "承載" 這片區域,而 Layer 內部的畫面更新一般不會影響到其他 Layer。那 Layer 是如何更新?
  • Flutter項目實戰:編寫一個非常精美的Flutter Todo-List項目
    工具類,比如文件操作等widgets封裝好的各種Widget結尾項目創建於6月21日,到如今發布1.0.0版本花了三十多天的時間,雖然我做過很多測試,解決了很多bug,但是時間確實不充裕。紕漏也會在所難免所以如果使用過程中遇到什麼問題,或者對於項目有什麼好的建議,歡迎在app中的反饋界面提出來,也可以在下面留下評論,又或者在github上提issue。
  • APP 開發從 0 到 1(一)需求與準備
    背景在《手把手教你做個人 app》我有說過,開發一個 APP 很大程度依賴服務端:服務端提供接口數據,然後 APP 展示;開發一個 APP,還需要美工協助切圖。對於以前的我,沒接口,沒美工,照樣可以開發 APP ,可謂 So easy 來形容。
  • Flutter 原理及美團實踐
    : "^0.1.2"  plugin1:     git:       url: "git://github.com/flutter/plugin1.git"  plugin2:     path: ..
  • 用前端最舒服的躺姿 "搞定" Flutter
    也就是說TextTheme API是向前兼容的,對於舊的廢棄名稱只做警告。同時新的 主題也可以搭配 新的Google Fonts for Flutter v1.0 字體。更加全面的工作,對滾動、文本框以及其他輸入 widget 的無障礙功能進行了修復。
  • 半小時帶你入門 Flutter
    Questions tagged [flutter]img本文我們從介紹flutter基本概念到梳理常用Widget到常用app demos編寫到~放棄~,希望可以幫助每一個像我一樣的初學者。有誤地方還望大神不吝賜教~
  • 3色 三色勁爆價格 頂級真爆Adidas ultra boost ub 3.0
    三色勁爆價格頂級真爆Adidas ultra boost ub 3.0Size 36 36.5 37 38 38.5 39 40 40.5 41 42 42.5 43 44 44.5 45 編碼B6003
  • 編寫一個非常精美的Flutter Todo-List項目
    intlintl語言包sqflite本地資料庫flutter_colorpicker取色框cached_network_image圖片緩存image_picker圖片選取permission_handler權限申請path_provider路徑獲取image_crop圖片裁剪flutter_svgsvg解析package_info獲取package信息flutter_webview_plugin網頁
  • 21天不夠,100天才是答案:從0到1不是件容易的事
    但是,對我們來說,有一個很重要的問題是:21天真的能讓我們從0到1嗎?在2020年年初的時候,我曾做過一次為期21天寫作訓練營,參與人數大概70人左右。在21天結束那天,我算了一下,能在這21天內每天都寫作的人有10位左右。而在21天結束之後,就幾乎沒有人再堅持去寫了。
  • 0.5+0.5=1
    張智霖一直對這件事表示非常後悔跟愧疚:「當初我本來是想大宴親朋,大家一起過一個好開心的千禧婚禮。但我沒有做到,甚至連一張婚紗相我們都沒有拍到。我覺得我是對她有所虧欠的,我好想約定她,當我們都老了的時候,六十歲,好不好,不如我們再拍一次吧。」張智霖真摯而深情的告白早已打動了袁詠儀也打動了所有的觀眾。
  • Flutter竟然還有這種高端用法?
    前倆篇文章就是咱們這些大抄子的主要「參考」的資料來源,這三篇文章在掘金上有翻譯版,搜下bloc就能找到。最後一篇文章就是我主要總結歸納的源泉,作者在官網上寫了好幾個demo:計時器,登錄,Todos,天氣等等,大家可以自己去看看。
  • 【讀一刻】《從0到1》連載1
    做大家都知道怎麼做的事、提供更多熟悉的東西,這是由1到n。不過如果只複製前人的路,就無法學習到他們的精髓。創新獨一無二,創新的時機與開創出的結果也是新鮮奇特的。PayPal公司創始人、Facebook第一位外部投資者彼得•蒂爾在本書中詳細闡述了自己的創業歷程與心得,包括如何避免競爭、如何進行壟斷、如何發現新的市場。《從0到1》為你開啟創新的秘密。
  • 《降魔的2.0》降魔的2.0(期待3.0)
    先給駿釗記海鮮飯店一個五星好評哈哈哈作為續集來說,2.0真的很成功,帶給的驚喜也很多,很久沒試過每天守著電視蹲開播。1.說說劇裡的聯動相比第一部,這一部的聯動可以說非常多了,是一個TVB宇宙,對於我個人而言,我非常樂於找其中的梗和彩蛋。張彥博,朱千雪,Jennie,楊明等客串。
  • Flutter - 不聽話的 Container
    痛定思過,我終於開始反抗(起來,不願做奴隸的人們,國歌唱起來~),為什麼 Container 設置寬高又無效了?Column 為什麼又溢出邊界了?懷揣著滿腔熱血,我終於鼓起勇氣首先從 Container 源碼入手,逐一揭開它的神秘面紗。
  • Flutter Widget - Container 布局詳解
    介紹Container 初用起來很簡單,但是裡面的邏輯又有些複雜,我也不敢說完全吃透,所以本文還是以總結網上各種文章為主,再加上自己的理解,如果有不對的地方,請一定指出在 Flutter 中,所有的功能都被分散成單一功能的 Widget,比如居中有 Center,邊框有 Padding,文字是 Text,手勢是 GestureDetector,他們各自維護一個功能,但是我們商業
  • 近視眼的福音,自創2+1組合,一周提升視力0.1-0.2
    我用這個方法一個星期視力提升了0.1-0.2。去年入職體檢的時候,我的視力還是0.8,左右眼都是。經過一年的工作摧殘,每天對著電腦寫報告,對著手機看電視。我的左眼降到了0.4,右眼降到了0.6。當時在眼科做眼部檢查的時候,醫生就說你是個近視眼。
  • HTTP1.0、HTTP1.1 和 HTTP2.0 的區別
    也是說對於前端來說,我們所寫的HTML頁面將要放在我們的 web 伺服器上,用戶端通過瀏覽器訪問url地址來獲取網頁的顯示內容,但是到了 WEB2.0 以來,我們的頁面變得複雜,不僅僅單純的是一些簡單的文字和圖片,同時我們的 HTML 頁面有了 CSS,Javascript,來豐富我們的頁面展示,當 ajax 的出現,我們又多了一種向伺服器端獲取數據的方法,這些其實都是基於 HTTP 協議的。