天天看點

flutter跨平台原理

React Native

最終渲染工作交還給了系統,雖然同樣使用類HTML+JS的UI建構邏輯,但是最終會生成對應的自定義原生控件,以充分利用原生控件相對于WebView的較高的繪制效率。

不僅架構本身需要處理大量平台相關的邏輯,随着系統版本變化和API的變化,開發者可能也需要處理不同平台的差異,甚至有些特性隻能在部分平台上實作,這樣架構的跨平台特性就會大打折扣。

Flutter

從頭到尾重寫一套跨平台的UI架構,包括UI控件、渲染邏輯甚至開發語言。渲染引擎依靠跨平台的Skia圖形庫來實作,依賴系統的隻有圖形繪制相關的接口,可以在最大程度上保證不同平台、不同裝置的體驗一緻性,邏輯處理使用支援AOT的Dart語言,執行效率也比JavaScript高得多。

Flutter所使用的Dart語言同時支援AOT和JIT運作方式,JIT模式下還有一個備受歡迎的開發利器“熱重新整理”(Hot Reload)

Flutter通過将新的代碼注入到正在運作的DartVM中,來實作Hot Reload這種神奇的效果,在DartVM将程式中的類結構更新完成後,Flutter會立即重建整個控件樹,進而更新界面。并不是所有的代碼改動都可以通過熱重新整理來更新:

1.編譯錯誤,如果修改後的Dart代碼無法通過編譯,Flutter會在控制台報錯

2.控件類型從StatelessWidget到StatefulWidget的轉換,因為Flutter在執行熱重新整理時會保留程式原來的state

3.全局變量和靜态成員變量,這些變量不會在熱重新整理時更新。

4.修改了main函數中建立的根控件節點,Flutter在熱重新整理後隻會根據原來的根節點重新建立控件樹,不會修改根節點。

5.某個類從普通類型轉換成枚舉類型,或者類型的泛型參數清單變化,都會使熱重新整理失敗。

熱重新整理無法實作更新時,執行一次熱重新開機(Hot Restart)就可以全量更新所有代碼,同樣不需要重新開機App,差別是restart會将所有Dart代碼打包同步到裝置上,并且所有狀态都會重置。

Flutter重寫了一套跨平台的 UI 架構,渲染引擎是依靠 Skia 圖形庫實作

Flutter 中的控件樹直接由渲染引擎和高性能本地 ARM 代碼直接繪制,不需要通過中間對象(Web 應用中的虛拟 DOM 和真實 DOM,原生 App 中的虛拟控件和平台控件)來繪制

Flutter插件

Flutter使用的Dart語言無法直接調用Android系統提供的Java接口,這時就需要使用插件來實作中轉。Flutter官方提供了豐富的原生接口封裝:

Dart本身提供了三種運作方式:

1.使用Dart2js編譯成JavaScript代碼,運作在正常浏覽器中(Dart Web)。

2.使用DartVM直接在指令行中運作Dart代碼(DartVM)。

3.AOT方式編譯成機器碼,例如Flutter App架構(Flutter)。

最終選擇Dart作為開發語言主要有幾個原因:

1.健全的類型系統,同時支援靜态類型檢查和運作時類型檢查。

2.代碼體積優化(Tree Shaking),編譯時隻保留運作時需要調用的代碼(不允許反射這樣的隐式引用),是以龐大的Widgets庫不會造成釋出體積過大。

3.豐富的底層庫,Dart自身提供了非常多的庫。

4.多生代無鎖垃圾回收器,專門為UI架構中常見的大量Widgets對象建立和銷毀優化。

5.跨平台,iOS和Android共用一套代碼。

6.JIT & AOT運作模式,支援開發時的快速疊代和正式釋出後最大程度發揮硬體性能。

DartVM的記憶體配置設定政策非常簡單,建立對象時隻需要在現有堆上移動指針,記憶體增長始終是線形的,省去了查找可用記憶體段的過程:

flutter跨平台原理

Dart中類似線程的概念叫做Isolate,每個Isolate之間是無法共享記憶體的,是以這種配置設定政策可以讓Dart實作無鎖的快速配置設定。

Dart的垃圾回收也采用了多生代算法,新生代在回收記憶體時采用了“半空間”算法,觸發垃圾回收時Dart會将目前半空間中的“活躍”對象拷貝到備用空間,然後整體釋放目前空間的所有記憶體:

flutter跨平台原理

整個過程中Dart隻需要操作少量的“活躍”對象,大量的沒有引用的“死亡”對象則被忽略,這種算法也非常适合Flutter架構中大量Widget重建的場景。

Flutter架構

從下到上依次為:Embedder(嵌入器)、Engine、Framework。

flutter跨平台原理

Embedder 是嵌入層,做好這一層的适配 Flutter 基本可以嵌入到任何平台上去; Engine 層主要包含 Skia、Dart 和 Text。Skia 是開源的二位圖形庫;Dart 部分主要包括 runtime、Garbage Collection、編譯模式支援等;Text 是文本渲染。Framework 在最上層。

Flutter 流水線包括 7 個步驟:

flutter跨平台原理

在渲染階段,控件樹(widget)會轉換成對應的渲染對象(RenderObject)樹,在 Rendering 層進行布局和繪制。

在布局時 Flutter 深度優先周遊渲染對象樹。資料流的傳遞方式是從上到下傳遞限制,從下到上傳遞大小。也就是說,父節點會将自己的限制傳遞給子節點,子節點根據接收到的限制來計算自己的大小,然後将自己的尺寸傳回給父節點。整個過程中,位置資訊由父節點來控制,子節點并不關心自己所在的位置,而父節點也不關心子節點具體長什麼樣子。

flutter跨平台原理

為了防止因子節點發生變化而導緻的整個控件樹重繪,Flutter 加入了一個機制——Relayout Boundary,在一些特定的情形下 Relayout Boundary 會被自動建立

例如,控件被設定了固定大小(tight constraint)、控件忽略所有子視圖尺寸對自己的影響、控件自動占滿父控件所提供的空間等等。很好了解,**就是控件大小不會影響其他控件時,就沒必要重新布局整個控件樹。**有了這個機制後,無論子樹發生什麼樣的變化,處理範圍都隻在子樹上。

flutter跨平台原理

在确定每個空間的位置和大小之後,就進入繪制階段。繪制節點的時候也是深度周遊繪制節點樹,然後把不同的 RenderObject 繪制到不同的圖層上。

這時有可能出現一種特殊情況,如下圖所示節點 2 在繪制子節點 4 時,由于其節點 4 需要單獨繪制到一個圖層上(如 video),是以綠色圖層上面多了個黃色的圖層。之後再需要繪制其他内容(标記 5)就需要再增加一個圖層(紅色)。再接下來要繪制節點 1 的右子樹(标記 6),也會被繪制到紅色圖層上。是以如果 2 号節點發生改變就會改變紅色圖層上的内容,是以也影響到了毫不相幹的 6 号節點。

flutter跨平台原理

為了避免這種情況,**Flutter 的設計者這裡基于 Relayout Boundary 的思想增加了 Repaint Boundary。**在繪制頁面時候如果遇見 Repaint Boundary 就會強制切換圖層。

如下圖所示,在從上到下周遊控件樹遇到 Repaint Boundary 會重新繪制到新的圖層(深藍色),在從下到上傳回的時候又遇到 Repaint Boundary,于是又增加一個新的圖層(淺藍色)。

Repaint Boundary 并不會像 Relayout Boundary 一樣自動生成,而是需要我們自己來加入到控件樹中。

【Widget】控件層

所有控件的基類都是 Widget,Widget 的資料都是隻讀的, 不能改變。是以每次需要更新頁面時都需要重新建立一個新的控件樹。每一個 Widget 會通過一個 RenderObjectElement 對應到一個渲染節點(RenderObject),可以簡單了解為 Widget 中隻存儲了頁面元素的資訊,而真正負責布局、渲染的是 RenderObject。

flutter跨平台原理

圖 7: Widget、Element 和 Render 之間的關系

如果想把方形的顔色換成黃色,将圓形的顔色變成紅色,由于控件是不能被修改的,需要重新生成兩個新的控件 Rectangle yellow 和 Circle red。由于隻是修改了顔色屬性,是以 Element 和 RenderObject 都被重用,而之前的控件樹會被釋放回收。

flutter跨平台原理

那麼如果把紅色圓形變成三角形又會怎樣呢?由于這裡發生變化的是類型,是以對應的 Element 節點和 RenderObject 節點都需要重新建立。但是由于黃色方形沒有發生改變,是以其對應的 Element 節點和 RenderObject 節點沒有發生變化。

flutter跨平台原理

最後是【Material】 & 【Cupertino】,這是在 Widget 層之上架構為開發者提供的基于兩套設計語言實作的 UI 控件,可以幫助我們的 App 在不同平台上提供接近原生的使用者體驗。

flutter跨平台原理

StatelessWidget:内部沒有儲存狀态,UI界面建立後不會發生改變;

StatefulWidget:内部有儲存狀态,當狀态發生改變,調用setState()方法會觸發StatefulWidget的UI發生更新,對于自定義繼承自StatefulWidget的子類,必須要重寫createState()方法。

三棵樹

flutter跨平台原理

Widget:負責建立Element,決定Element是否需要更新,Flutter Framework通過差分算法比對Widget樹前後的變化,決定Element的State是否改變。當重建Widget樹後并未發生改變, 則Element不會觸發重繪

Element:表示Widget配置樹的特定位置的一個執行個體,同時持有Widget和RenderObject,負責管理Widget配置和RenderObject渲染。Element狀态由Flutter Framework管理, 開發人員隻需更改Widget即可。

RenderObject:表示渲染樹的一個對象,負責真正的渲染工作,比如測量大小、位置、繪制等都由RenderObject完成。

渲染原理

flutter跨平台原理

UI線程完成布局、繪制操作,生成Layer Tree;GPU線程執行合成并光栅化後交給GPU來處理,其中幾個關鍵步驟:

Animate: 周遊_transientCallbacks,執行動畫回調方法;

Build: 對于dirty的元素會執行build構造,沒有dirty元素則不會執行,對應于buildScope()

Layout: 計算渲染對象的大小和位置,對應于flushLayout(),這個過程可能會嵌套再調用build操作;

Compositing bits: 更新具有髒合成位的任何渲染對象, 對應于flushCompositingBits();

Paint: 将繪制指令記錄到Layer, 對應于flushPaint();

Compositing: 将Compositing bits發送給GPU, 對應于compositeFrame();

Flutter産物分為Dart業務代碼和Engine代碼各自生成的産物,圖中的Dart Code包含開發者編寫的業務代碼,Engine Code是引擎代碼

flutter跨平台原理

一份Dart代碼,可編譯生成雙端産物,Android産物是由vm、isolate各自的指令段和資料段以及flutter.jar組成的app.apk,iOS産物是由App.framework和Flutter.framework組成的Runner.app。

frontend_server前端編譯器,将dart代碼轉換為AST(抽象文法樹),并生成app.dill格式的dart kernel

Flutter TaskRunner

Flutter的任務隊列處理機制跟Android的消息隊列處理相通,隻不過Flutter分為Task和MicroTask兩種類型,引擎和Dart虛拟機的事件以及Future都屬于Task,Dart層執行scheduleMicrotask()所産生的屬于Microtask。

flutter跨平台原理

Step 1: 檢查task,當task隊列不為空,先執行一個task;

Step 2: 檢查microTask,當microTask不為空,則執行microTask;不斷循環Step 2 直到microTask隊列為空,再回到執行Step 1;

可簡單了解為先處理完所有的Microtask,然後再處理Task。因為scheduleMicrotask()方法的調用自身就處于一個Task,執行完目前的task,也就意味着馬上執行該Microtask。

isolate之間是邏輯隔離的,Isolate中的代碼也是按順序執行,因為Dart沒有共享記憶體的并發,沒有競争的可能性,故不需要加鎖,也沒有死鎖風險。對于Dart程式的并發則需要依賴多個isolate來實作。

flutter如何調用原生代碼

Flutter通過提供Platform Channel的功能,使得Dart代碼具備與Native互動的能力。

Platform Channel用于Flutter與Native之間的消息傳遞,整個過程的消息與響應是異步執行,不會阻塞使用者界面。Flutter引擎架構已完成橋接的通道,這樣開發者隻需在Native層編寫定制的Android/iOS代碼,即可在Dart代碼中直接調用

flutter跨平台原理