Flutter 到底是如何渲染的?

以前,寫了一篇《iOS 淺談GPU及「App渲染流程」》闡述了iOS端App的渲染流程。
其中包括三種渲染方式,分別是:
1.iOS原生渲染:使用原生控件、原生語言編寫。
2.大前端渲染(一種是WebView,另外一種是JSCore引擎做爲虛擬機)。
3.Flutter渲染。前端

《上篇博客》主要講解了iOS APP渲染的流程,以及GPU的渲染流水線。
但關於Flutter是如何渲染的?我上篇寫的並非很透徹。
因此本篇將和你們一塊兒談談 —— 「 Flutter 到底是如何渲染的? 」api


1、Flutter 的架構

先從Flutter的總體架構提及,共分爲三層,又下到上分別爲:Embedder層、Engine層、Framework層。架構

分別對應:佈局

  • Embedder層:操做系統底層適配層。
  • Engine層:渲染引擎層。
  • Framework層:用Dart實現的上層UI SDK。

Flutter架構

那麼,這三層的職責是什麼?究竟分別作了哪些事呢?性能

從上到下開始,先從咱們相對比較熟悉的 Framework 層提及。測試

1. Framework層:Dart實現的上層UI SDK

實現了:Animation(動畫)、Painting(圖形繪製)、Gestures(手勢操做)等功能,幷包裝成對應的 api 提供給上層開發者調用。
爲了保證Flutter所繪製的控件與原生控件風格相似,Flutter封裝了Material(對應Android)、Cupertino(對應iOS)風格的UI組件庫,供開發者直接使用。優化

2. Engine層:Skia渲染 + DartVM 引擎

這層主要包含三塊:SkiaDartText
Skia 是渲染引擎,爲 Framework 層提供「底層渲染」能力。
DartDart 運行時引擎,爲 Framework 層提供運行時調用Dart和渲染能力。
Text 是文字排版,爲 Framework 層提供視圖排版能力。動畫

3. Embedder層:操做系統適配

對不一樣平臺操做系統的適配,包括一些配置:surface、線程、插件等特性。
因爲Flutter相關特性並很少,所以對不一樣平臺操做系統的適配成本很低。操作系統

2、Flutter 的 渲染策略

咱們都知道,在Flutter中,Everything is widget.net

全部 Widget 會組成 Widget Tree
界面更新時,會更新 Widget Tree
再更新 Element Tree ,最後更新 RenderObjectTree

那麼,究竟Flutter是怎麼更新界面的?又有哪些優化的渲染策略呢??

分爲4個階段,分別是:

佈局階段 => 繪製階段 => 合成階段 => 渲染階段
Layout => Paint => Composite => Rasterize

1. 佈局(Layout)

Flutter採用 「深度優先」 機制遍歷Widget Tree。

咱們都知道,在佈局過程當中,Widget樹中的每一個孩子節點都會受到父節點所加的位置約束。(也就是說,父節點決定孩子節點的位置,孩子節點只能決定本身的大小。)

先肯定父節點的大小、位置,
再肯定孩子節點的位置,
再肯定孩子節點的大小。
....一直往下,
直到沒有孩子Widget後,返回完成。

Layout

PS:深度優先於廣度優先的區別:參考博客

爲了防止孩子節點的變化,致使整個 Widget Tree 從新佈局。
Flutter加入了 「佈局邊界」 機制(Relayout Boundary)。(劃重點,★優化點★)

在某些節點,「自動」或「手動」加上 「佈局邊界」 ,控制邊界。
在該佈局邊界內的任何節點發生從新佈局,都不會影響邊界外的 Widget Tree的佈局。

Relayout Boundary

佈局完成後,樹上每一個節點都肯定了「尺寸大小」和「位置」。

2. 繪製(Paint)

剛纔,咱們肯定了樹上的控件的「尺寸」與「位置」。
接下來是繪製階段。

和佈局相似,Flutter也是採用 「深度優先」 機制遍歷渲染樹。
先繪製自身,再繪製子節點。

Paint

可是,會出現一些繪製圖層覆蓋問題。
好比,當節點2須要重繪時,節點二、五、6一塊兒重繪了(其實只要重繪節點二、5)。

Repaint Boundary

爲了解決繪製覆蓋問題,Flutter採用了也是和佈局階段類似的策略: 重繪邊界 機制(Repaint Boundary)。
其實,本質上就是加個新的圖層,避免在同一圖層重繪產生影響。(劃重點,★優化點★)

典型的例子是,ScrollView。
一旦設置好重繪邊界,滾動時,只會重繪ScollView中的視圖內容,而其餘部分不用從新繪製。(劃重點,★優化點★)

3. 合成(Composite)

因爲繪製出來的渲染樹,會有不少層,同步多層渲染會出現性能問題。
所以,Flutter會在渲染前,將多個渲染樹圖層進行合成。(劃重點,★優化點★)
根據多層渲染樹的大小、層級、透明度等計算後,
合成爲最終「簡化版」的渲染樹,以提升下一步的渲染效率。

4. 渲染(Rasterize)

將處理過的「簡化版」渲染樹,交給Skia引擎轉換成「二維圖像數據」。
而後 Skia 把計算好的圖形數據,經過 OpenGL 接口交給 GPU 渲染,走上一篇《iOS 淺談GPU及「App渲染流程」》中提到的 GPU 工做流水線:
頂點着色器 => 形狀裝配 => 幾何着色器 => 光柵化 => 片斷着色器 => 測試與混合。

而後,GPU工做流水線六階段完成。

最終,展現到咱們的終端屏幕上。

固然這只是一個垂直同步信號(VSync)的過程。(按60fps算,一秒須要60個VSync纔不會感到卡頓。)

3、Flutter 渲染工做流水線

按60fps算,一秒須要60個VSync纔不會感到卡頓。

Flutter渲染流水線

在流水線的Widget更新,Flutter也有優化的點:

簡單來講,就是chind節點能複用就複用。不能複用,就從新繪製。

更新Widget的邏輯以下:

\ newWidget == null newWidget != null
child == null 返回null 返回新的Element
child != null 移除舊的child並返回null 若是舊child被更新就返回child,不然返回新的Element

參考與致謝:
1.《Flutter核心技術與實戰》(陳航老師)
2. 小德 -- Flutter 技術分享
3.《Flutter從加載到顯示》 (聖文前輩)

相關文章
相關標籤/搜索