<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Flutter混合棧路由實踐與優(yōu)化

          共 5054字,需瀏覽 11分鐘

           ·

          2021-03-26 20:17

          導(dǎo)語 | 在 Flutter 和原生混合開發(fā)的場景里,路由是繞不開的一個話題。但業(yè)內(nèi)的方案中仍存在內(nèi)存異常,對官方底層的修改也需要不斷踩坑。我們在項目實踐中,抽離出了一套混合棧路由框架。對內(nèi)存進行了進一步優(yōu)化,清晰了對底層代碼的修改,同時更易于 Flutter SDK 升級。文章作者:李鵬飛,騰訊IEG前端研發(fā)工程師。


          一、背景及綜述


          Flutter 在目前跨平臺方案中有更好的平臺一致性以及更優(yōu)的體驗。但對于本身已有成熟的業(yè)務(wù)代碼的項目來說,更多的是采用混合棧的方式,在不變更原有 App 業(yè)務(wù)的基礎(chǔ)上,將 Flutter 能力擴展為子模塊進行接入和開發(fā)。這樣并不影響原有的業(yè)務(wù)和原生能力,又可以結(jié)合業(yè)務(wù)需求進行技術(shù)選擇。


          混合棧涉及到 Flutter 頁面與原生頁面的跳轉(zhuǎn)。而官方的路由方案,在多引擎下有著通信隔離,資源不共享,極大的內(nèi)存損耗等缺陷。

          業(yè)內(nèi)采用較廣泛是單引擎復(fù)用方案,但這仍有不少痛點,體現(xiàn)在兩個方面:

          • 混合棧路由在使用時,仍有內(nèi)存異常;

          • 底層代碼的修改,需要不斷踩坑。


          為了解決這些問題,心悅抽離出了一套混合棧路由框架 TRouter。

          • 單引擎下內(nèi)存進一步優(yōu)化,解決了打開多個 Flutter 頁面時內(nèi)存異常增長(Boost 等方案下仍有內(nèi)存異常);

          • 規(guī)避底層代碼修改不可見導(dǎo)致的項目風(fēng)險,解決過度耦合 io.flutter 包導(dǎo)致的 sdk 更新困難。


          本文的目標(biāo)是闡述 Flutter 實踐混合棧路由中遇到的痛點,以及 TRouter 是如何去解決的。最后會對目前的方案進行橫向?qū)Ρ龋v述下一步的計劃。

          二、混合集成面臨的問題


          項目最終明確選用了單引擎復(fù)用的方案,業(yè)內(nèi)未解決而我們面臨的痛點有兩個:
          1. iOS側(cè)的內(nèi)存增長異常;
          2. Android側(cè) 底層修改不透明給項目帶來風(fēng)險。
          在介紹TRouter之前,本節(jié)會討論問題的成因,以及為什么說業(yè)內(nèi)方案存在缺陷。
          官方并沒有很好解決混合棧路由所遇到的問題

          Flutter 的技術(shù)鏈路是建立在 C++ 編寫的 Engine 和 Dart 編寫的 Framework 層組成。主要構(gòu)成如下圖所示:


          可以明確的是:

          • Engine 管理著 Flutter 所使用的四個線程,本身是一個較重的一個對象。

          • isolate 管理著 Dart 層內(nèi)存和單線程控制的運行實體。isolate 本身意思是“隔離”,每個 isolate 之間的內(nèi)存和邏輯是隔離的,所以對應(yīng)的 Engine 也是資源不共享的。

          • Engine 依賴于原生的某個視圖組件提供渲染的能力,比如純 Flutter 應(yīng)用就只在單獨一個 Activity/ViewController 上創(chuàng)建了 Engine 以提供 Flutter 的視圖渲染。


          在混合棧路由上,雖然 Dart 層本身有提供 navigator 等路由方式,但當(dāng)我們把 Flutter 集成為原生的模塊或能力時,一定會出現(xiàn) Native -> Flutter -> Native -> Flutter… 這種混合頁面跳轉(zhuǎn)情況。


          這樣存在問題是:如何保存 Flutter 頁面的狀態(tài),并且在頁面回退或跳轉(zhuǎn)時,在正確的時機恢復(fù)或切換 Flutter 的渲染內(nèi)容

          1. 多引擎方案


          Google 官方提供的是 keep it simple 的方案,即間隔的 Flutter 頁面單獨使用一個新的 Engine 來單獨維持一份視圖渲染,跳轉(zhuǎn)時就無需考慮 Dart 層頁面切換。

          這種方案弊端很多,首先是 Engine 的線性增多,帶來內(nèi)存的極大損耗。如下圖所示,Android 端多引擎下打開 5 個頁面內(nèi)存增量對比:


          其次由于 isolate 隔離,Dart 側(cè)圖片緩存等資源也無法共享,所有通信都需要經(jīng)過原生,使通信有極高的復(fù)雜度

          所以多引擎不能滿足項目的性能要求。

          2. 單引擎瀏覽器方案


          由于多引擎的缺陷,業(yè)內(nèi)的做法一般是對 isolute 或 Engine 進行復(fù)用來解決。影響力較大的是以 FlutterBoost 和 Thrio 為代表的單引擎瀏覽器方案。

          即把 Activity/ViewController 作為承載 Dart 頁面的瀏覽器,在頁面切換時對單引擎進行 detach/attach,同時通知 Dart 層頁面切換,來實現(xiàn) Engine 的復(fù)用。


          Thrio與Boost區(qū)別在于:在Flutter頁面連續(xù)跳轉(zhuǎn)時,只使用同一個 Activity/ViewController 承載。


          由于只持有了一個 Engine 單例,僅創(chuàng)建一份 isolate,Dart 層是通信和資源共享的,內(nèi)存損耗也得以有顯著的降低。下圖所示是 Android 側(cè)單引擎下打開 5 個頁面內(nèi)存增量對比:


          可以看出 Android 側(cè)跳轉(zhuǎn) Flutter 頁面的內(nèi)存消耗已降低到接近原生。

          痛點一:iOS側(cè)內(nèi)存增長異常


          在 iOS 側(cè),我們發(fā)現(xiàn)了打開新的承載 Flutter 頁面的 ViewController 仍會有 10M 左右的內(nèi)存增量


          對此,Boost 的建議是同一時間下,人為控制 Flutter 頁面在 5 個以內(nèi),來避免內(nèi)存過大的問題。哈啰單車的 Thrio 就是在 Boost 基礎(chǔ)上提出的優(yōu)化方案,即在 Flutter->Flutter 的情景下,避免創(chuàng)建 ViewController,而是在 Dart 層進行路由切換。但可以看出,該方案在增加雙端路由復(fù)雜度的同時,并沒有解決 Native->Flutter 的內(nèi)存大幅增長。

          這兩個方案都沒有真正解決內(nèi)存的異常問題。

          痛點二:Android側(cè),底層不可見的修改給項目帶來風(fēng)險


          此外,在 Android 側(cè),單引擎實現(xiàn)依賴于修改官方的 io.flutter 包。但我們并不清楚外部方案具體做了哪些底層修改,這給項目帶來風(fēng)險

          在預(yù)研單引擎路由方案的時候,我們發(fā)現(xiàn)大多是直接拉取官方 io.flutter 包來進行底層改造。這對于使用者就像一個黑盒子,并不知道什么地方做了什么修改,對出現(xiàn)的 bug 更無法排查。并且這種耦合依賴 io.flutter 包的方式,也會對 Flutter SDK 升級帶來困難。

          事實上,Github上 Boost 目前仍還有 160+ 的 issue 未解決,支持 Flutter SDK 版本的更新速度也不盡人意。所以我們打算自己踩一遍坑,尋求對官方代碼最小的修改,并使修改可見,來保證路由的穩(wěn)定性,問題可排查性。


          三、實現(xiàn)方式及痛點解決



          在明確業(yè)內(nèi)方案和面臨的痛點之后。我們聚焦于痛點的解決,推出了一套更優(yōu)的混合棧路由方案 TRouter。

          1. 整體框架


          整體框架上,仍采用單引擎瀏覽器方案。用 Activity/ViewController 承載 Dart 頁面的方式,把路由收歸原生,維持唯一的單引擎實例。

          在頁面生命周期變更時對單 Engine 進行 attach/detach,同時傳遞 url、params 通知 Dart 層進行頁面切換。


          值得注意的是,Dart 和 Native 層是職責(zé)分離的。

          Dart 層只負責(zé)接收原生端生命周期信息,并得到頁面的 url 與 params,來進行 Flutter 的頁面渲染。

          而 Native 層統(tǒng)一接管了頁面的跳轉(zhuǎn)和 url 解析,在跳轉(zhuǎn) Flutter 頁面時,感知上仍是打開一個 Activity/ViewController。

          這樣,混合棧路由與原生路由的體驗并無區(qū)別,可以輕松接入原有項目的路由邏輯。

          2. 內(nèi)存優(yōu)化


          iOS 端即使實現(xiàn)了單引擎復(fù)用,但仍會在創(chuàng)建 Flutter ViewContoller 時有 10M 的內(nèi)存異常增長。這就需要我們從底層來理解 Flutter 的渲染過程。

          Flutter 渲染是由 Vsync 信號觸發(fā) UI 刷新,再在 Dart 層進行 Widget 布局、繪制生成 LayerTree。然后渲染線程進行柵格化及合成,最終把渲染的結(jié)果設(shè)置到 layer.contents 里進行屏幕顯示。


          定位到最后一步,由于渲染出的結(jié)果是位圖,內(nèi)存占用比較大。當(dāng)每次新建一個 FlutterViewController 時會有一個渲染后的位圖與之對應(yīng),會導(dǎo)致每次新增一個頁面時會有一個較大的內(nèi)存增長


          由此,可以確定內(nèi)存的優(yōu)化思路。即在頁面完全退出(viewDidDisappear)后,將 FlutterView.layer.contents 對象設(shè)置為 nil,回收當(dāng)前頁面的位圖對象,在頁面即將展示(viewWillAppear)時重新渲染出新頁面

          這樣,在保證路由體驗的同時,避免了 iOS 側(cè)的內(nèi)存異常。優(yōu)化效果如下:


          在連續(xù)打開 Flutter 頁面里,內(nèi)存也能平穩(wěn)保持在正常水平。


          3. 底層改造


          Android 端 io.flutter 包的代碼,并沒有支持 Engine 的復(fù)用,所以會涉及到官方代碼的修改。

          從項目風(fēng)險考慮,我們在方案設(shè)計時有三個核心的訴求:

          • 對官方代碼做最小的修改,避免有引入額外 bug 的風(fēng)險;

          • 對代碼的變更是明確清晰的,在遇到線上問題時,可以第一時間進行分析和排查;

          • 可復(fù)用的訴求,易于 Flutter SDK 的迭代更新。


          在理解底層代碼和不斷踩坑后,我們明確了 Engine 可以在外部初始化,并且對引擎切換的代碼修改是有限的,這是實現(xiàn)訴求的前提。最終我們把底層改造邏輯分離,集合到 FlutterFixPlugin 插件里。 

          使用操縱字節(jié)碼 Hook 的方式,把每一個問題點的修改封裝為一個策略,一個策略包含多個代碼改動片段,從而達到改動可見,與 SDK 版本適配的目的


          FlutterFixPlugin 插件對代碼的改造是非侵入式的,僅需要在 .gradle 文件中進行依賴。

          apply plugin: 'com.tencent.fixflutter'

          插件支持根據(jù)不同 Flutter 版本進行策略的增減與變更,工程結(jié)構(gòu)如下


          方案優(yōu)勢體現(xiàn)在如下兩方面:

          (1)修改可見和問題覆蓋

          可以清晰明確底層代碼的修改內(nèi)容,并細分到了每條執(zhí)行語句。到目前為止,除開對 Engine 復(fù)用的必要修改外,插件已經(jīng)對跳轉(zhuǎn)時頁面跳屏,頁面白屏,跳轉(zhuǎn)時動畫不延續(xù)的等問題以及一些官方 issue 進行了適配修改。

          (2)多版本的支持

          得益于對 io.flutter 包非侵入式修改,我們驗證了 Flutter SDK v1.17、v1.20、v1.22,v2.0 等版本上,都可以良好運行。


          4. 方案對比


          最后,對方案進行一次對比總結(jié):


          總結(jié)來看,TRouter 混合棧的路由優(yōu)勢在于:

          • 路由方式簡單,Dart 層資源共享,有更優(yōu)的內(nèi)存性能表現(xiàn);

          • 項目風(fēng)險可控,底層代碼修改是可見的,F(xiàn)lutter SDK 版本適配更易行。


          四、下一步做的事情



          Flutter v2.0 升級與 View 級別的支持


          3月4日,Google 發(fā)布 Flutter v2.0 穩(wěn)定版,除了對 Web 更高質(zhì)量的支持與引入空安全外。其中一個重要更新就是提供了多引擎下使用 FlutterEngineGroup 來創(chuàng)建新的 Engine,官方宣稱內(nèi)存損耗僅占 180K。

          其本質(zhì)是使 Engine 可以共享 GPU 上下文、font metrics 和 isolate group snapshot,從而實現(xiàn)了更快的初始速度和更低的內(nèi)存占用。

          雖然目前看起來仍未穩(wěn)定,也有比較多的問題尚未解決,比如 Dart 層還是是資源隔離的,一套圖片資源可能被加載多次。但這讓我們看到了混合棧路由回歸官方方案的可能。

          下一步我們將繼續(xù)探究 v2.0 的特性,用 v2.0 對多引擎的加持來實現(xiàn) View 級別的支持。


          結(jié)語


          TRouter 是心悅項目解決 Flutter 路由痛點后的產(chǎn)物。在最開始的接入時,我們想法是能引入穩(wěn)定可靠的方案,但官方對混合棧的支持偏向薄弱。

          而從流傳的文章來看,業(yè)內(nèi)的方案跟隨 Flutter 版本的更新也不斷的在調(diào)整。最后應(yīng)該會趨近于同一套被廣泛認可的方式。

          從這一角度上講,所有技術(shù)都是不斷演進的,最終導(dǎo)向的是更高的性能表現(xiàn),與最佳的項目實踐。

          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲欧美国产视频 | 一区二区无码在线 | 久久亚洲AV成人无码国产人妖 | 天堂资源视频 | 久久黄色视频免费看 |