Flutter 高效能原理淺析

北斗星_And發表於2019-07-29

這是我第三篇Flutter相關部落格 歡迎 檢視我的前兩篇 Flutter實現篇

前言

Flutter是Google用以幫助開發者在Ios和Android兩個平臺開發高質量原生應用的全新移動UI框架.我開始認識Flutter時,經歷了三個Flutter重要歷史版本.

  • 2018年2月27日,在2018世界移動大會上,Google釋出了Flutter的第一個Beta版本。
  • 2018年6月21日,在全球大前端技術大會上,釋出了第一個Release Perview 1版本。
  • 2018年12月5日第一個Release 1.0版本釋出.

此後越來越多的人開始關注到Flutter。

在 Flutter 誕生之前,已經有許多跨平臺 UI 框架的方案,比如基於 WebView 的 Cordova、AppCan 等,還有使用 HTML+JavaScript 渲染成原生控制元件的 React Native、Weex 等。Flutter 則開闢了一種全新的思路,從頭到 尾重寫一套跨平臺的 UI 框架,包括 UI 控制元件、渲染邏輯甚至開發語言。

Flutter框架

Flutter 高效能原理淺析

從圖中可以看出 Flutter主要被分為兩層 Framework層和Flutter Engine.

Framework層全部使用Dart編寫,有完整UI框架的API,並預寫了Android(MaterialDesign)和IOS的(Cupertino)風格的UI,極大方便了開發移動端.

Framework 底層是 Flutter 引擎, 引擎主要負責圖形繪製 (Skia)、 文字排版 (libtxt) 和提供 Dart 執行時, 引擎全部使用 C++實現.

Flutter高效能原理

與其他跨平臺框架對比

在看Flutter框架前,我們先看一下其他跨平臺框架的設計

Flutter 高效能原理淺析
看Hybrid的架構,我們可以知道UI層的渲染是基於Webview去渲染,他的效能取決於webview的渲染效能,目前已知webview渲染效能 < NativeUI的效能

Flutter 高效能原理淺析
RN/Weex 的架構中,是基於Native的UI框架去適配,中間多了一層js轉NativeUI的過程

Flutter 高效能原理淺析
而Flutter不需要中間層(Webview,js 轉NativeUI這個過程),他是基於影象渲染引擎去直接繪製UI.

Dart 對於UI框架的高效能支援

我們知道Flutter的Framework層是使用了Dart語言編寫,那Dart語言有哪些優勢呢?下面分為幾個點來闡述

Dart記憶體分配機制

DartVM的記憶體分配策略非常簡單,建立物件時只需要在現有堆上移動指標,記憶體增長始終是線形的,省去了查詢可用記憶體段的過程

Flutter 高效能原理淺析

Dart中類似執行緒的概念叫做Isolate,每個Isolate之間是無法共享記憶體的,所以這種分配策略可以讓Dart實現無鎖的快速分配。

Dart 垃圾回收機制

Dart的垃圾回收也採用了多生代演算法,新生代在回收記憶體時採用了“半空間”演算法,觸發垃圾回收時Dart會將當前半空間中的“活躍”物件拷貝到備用空間,然後整體釋放當前空間的所有記憶體如圖.

Flutter 高效能原理淺析

整個過程中Dart只需要操作少量的“活躍”物件,大量的沒有引用的“死亡”物件則被忽略,這種 多生代無鎖垃圾回收器,專門為UI框架中常見的大量Widgets物件建立和銷燬優化,非常適合Flutter框架中大量Widget重建的場景.

Dart 編體積優化,及編譯JIT和AOT支援

程式碼體積優化(Tree Shaking),編譯時只保留執行時需要呼叫的程式碼(不允許反射這樣的隱式引用),所以龐大的Widgets庫不會造成釋出體積過大。

Dart支援兩種編譯模式:

  • JIT編譯 Just In Time Compiler -即時編譯
  • AOT編譯Ahead Of Time 預編譯

在debug模式下使用JIT編譯,生成srcipt/bytecode進行解釋執行,可以支援HotReload(熱過載),修改程式碼,保持即可在裝置上看到效果. 而在Release下 AOT編譯生成Machine Code,高效的執行.

Dart 單執行緒 非同步訊息機制

客戶端互動簡述

對於移動端的互動來說,大多數情況下都是在等待狀態,等待網路請求,等待使用者輸入等.那麼設想一下,發起一個網路請求只在一個執行緒中可以進行嗎?當然網路請求肯定是非同步的(注意這裡說的非同步而多執行緒並非一個概念.),事實驗證是可以的,Flutter就採用了Dart這種單執行緒機制,省去了多執行緒上下文切換帶來的效能損耗.(對於高耗時操作,也同樣支援多執行緒操作,通過Isolate開啟,不過注意這裡的多執行緒,記憶體是無法共享的.)

Dart 非同步訊息原理

當一個Dart的方法開始執行時,他會一直執行直至達到這個方法的退出點。換句話說Dart的方法是不會被其他Dart程式碼打斷的。 當一個Dart應用開始的標誌是它的main isolate執行了main方法。當main方法退出後,main isolate的執行緒就會去逐一處理訊息佇列中的訊息。

Flutter 高效能原理淺析

有了訊息佇列,然後有了迴圈去讀取訊息佇列中的訊息,就可以有單執行緒去執行非同步訊息的能力. 一般的訊息使用dart:async中使用Future來支援非同步訊息.

Flutter Engine 高效能

在講Flutter Engin層時,我們先講一下螢幕繪製的原理.

螢幕繪製原理

Flutter 高效能原理淺析

我們都知道顯示器以固定的頻率重新整理,比如 iPhone的 60Hz、iPad Pro的 120Hz。當一幀影象繪製完畢後準備繪製下一幀時,顯示器會發出一個垂直同步訊號(VSync),所以 60Hz的螢幕就會一秒內發出 60次這樣的訊號。

並且一般地來說,計算機系統中,CPU、GPU和顯示器以一種特定的方式協作:CPU將計算好的顯示內容提交給 GPU,GPU渲染後放入幀緩衝區,然後視訊控制器按照 VSync訊號從幀緩衝區取幀資料傳遞給顯示器顯示。

作為一個專職Android開發,看過Android的繪圖機制,通過SurfaceFlinger 和HAL層之間的工作機制發現和Flutter的很像,那麼IOS的如何呢?個人推測螢幕的繪圖機制是一樣的,只是不同平臺有不同實現.

Flutter Engine的渲染機制

Flutter 高效能原理淺析

Flutter只關心向 GPU提供檢視資料,GPU的 VSync訊號同步到 UI執行緒,UI執行緒使用 Dart來構建抽象的檢視結構,這份資料結構在 GPU執行緒進行圖層合成,檢視資料提供給 Skia引擎渲染為 GPU資料,這些資料通過 OpenGL或者 Vulkan提供給 GPU.

所以 Flutter並不關心顯示器、視訊控制器以及 GPU具體工作,它只關心 GPU發出的 VSync訊號,儘可能快地在兩個 VSync訊號之間計算併合成檢視資料,並且把資料提供給 GPU.

Flutter Framework層的繪圖機制

UI樹原理

Flutter 高效能原理淺析

在 Flutter 介面渲染過程分為 3 個階段: 佈局、繪製、合成.

而佈局階段,有三個重要的物件.RenderObject、Element、Widget.

Flutter 高效能原理淺析

  • Widget是開發經常接觸的控制元件,預設是隻讀的.

  • Element 是 Flutter 用來分離控制元件樹和真正的渲染 物件的中間層, 控制元件用來描述對應的 element 屬性,控制元件重建後可能會複用同一個 element.

  • RenderObject 負責提供配置資訊並建立具體的 Element。

Element 持有真正負責佈局、 繪製和碰撞測試 (hit test) 的 RenderObject 物件.

那麼這樣,如果控制元件的屬性發生了變化 (因為控制元件的屬性是隻 讀的, 所以變化也就意味著重新建立了新的控制元件樹), 但是其樹上每個節點的型別沒有變化時, element 樹和 render 樹可以完全重用原來的物件 (因為 element 和 render object 的屬性都是可變的)

佈局原理

傳統佈局,如Android可能需要多次Measure,計算寬高。Flutter 採用約束進行單次測量佈局. 整個佈局過程中只需要深度遍歷一次,極大的提升效能。

Flutter 高效能原理淺析

渲染物件樹中的每個物件都會在佈局過程中接受父 物件的 Constraints 引數,決定自己的大小, 然後父物件 就可以按照自己的邏輯決定各個子物件的位置,完成佈局過程.

子物件不儲存自己在容器中的位置, 所以在它的位置發生改變時並不需要重新佈局或者繪製. 子物件的位 置資訊儲存在它自己的 parentData 欄位中,但是該欄位由它的父物件負責維護,自身並不關心該欄位的內容。

同時也因為這種簡單的佈局邏輯, Flutter 可以在某些節 點設定佈局邊界 (Relayout boundary), 即當邊界內的任 何物件發生重新佈局時, 不會影響邊界外的物件, 反之亦然.

參考資料

文中參考的資料如下

相關文章