Flutter渲染之Widget、Element、RenderObject
作者丨xiangzhihong
來源:
https://segmentfault.com/a/1190000039730403
一、Flutter架構(gòu)
眾所周知,F(xiàn)lutter是由Google推出的開源的高性能跨平臺框架,一個(gè)2D渲染引擎。在Flutter中,Widget是Flutter用戶界面的基本構(gòu)成單元,可以說一切皆Widget。與Weex和RN框架使用的JsCore轉(zhuǎn)化的中間層不同,F(xiàn)lutter采用的是全新的架構(gòu)方案,擁有自己的渲染引擎和Dart上層,每一層都建立在前一層的基礎(chǔ)之上,并且上層比下層的使用頻率更高,其框架架構(gòu)如下圖所示。

可以看到,自下而上,F(xiàn)lutter分為Embedder、Engine和Framework三層。其中,Embedder是操作系統(tǒng)適配層,主要負(fù)責(zé)Surface渲染設(shè)置,線程設(shè)置,以及平臺插件等平臺相關(guān)特性的適配;Engine層負(fù)責(zé)圖形繪制、文字排版和提供Dart運(yùn)行時(shí),Engine層具有獨(dú)立虛擬機(jī),正是由于它的存在,F(xiàn)lutter程序才能運(yùn)行在不同的平臺上,實(shí)現(xiàn)跨平臺運(yùn)行;Framework層則是使用Dart編寫的一套基礎(chǔ)視圖庫,包含了動畫、圖形繪制和手勢識別等功能,是使用頻率最高的一層。
Flutter Embedder:Embedder是Flutter的操作系統(tǒng)適配層,又稱為嵌入層,通過該層可以把Flutter嵌入到各個(gè)不同的平臺上去。Embedder的主要工作包括Surface渲染設(shè)置、線程設(shè)置、事件循環(huán)以及插件的平臺適配等。
Flutter Engine:純 C++實(shí)現(xiàn)的 SDK,其中包括 Skia引擎、Dart運(yùn)行時(shí)、文字排版引擎等。它是 Dart的一個(gè)運(yùn)行時(shí),它可以以 JIT 或者 AOT的模式運(yùn)行 Dart代碼。這個(gè)運(yùn)行時(shí)還控制著 VSync信號的傳遞、GPU數(shù)據(jù)的填充等,并且還負(fù)責(zé)把客戶端的事件傳遞到運(yùn)行時(shí)中的代碼。
Flutter Framework:純 Dart實(shí)現(xiàn)的 SDK,提供了一整套自底向上的基礎(chǔ)庫, 用于處理動畫、繪圖和手勢。并且基于繪圖封裝了一套 UI組件庫,然后根據(jù) Material 和Cupertino兩種視覺風(fēng)格區(qū)分開來。在平時(shí)應(yīng)用開發(fā)中,與開發(fā)者打交道最多的就是這一層,并且最多的就是各種Widget。
二、渲染流程
不管是什么渲染框架,其基本的原理都是:一般以60Hz的固定頻率刷新,每一幀圖像繪制完成后,會繼續(xù)繪制下一幀,然后顯示器就會發(fā)出一個(gè)Vsync信號,按60Hz計(jì)算,屏幕每秒會發(fā)出60次這樣的信號。CPU計(jì)算好顯示內(nèi)容提交給GPU,GPU渲染好交給顯示器顯示。
在Flutter中,渲染會用到很多的線程,主要是UI線程和GPU線程,下圖是Flutter App線程的運(yùn)作原理圖。
下面重點(diǎn)看一下UI線程和GPU線程。
UI Task Runner
UI Task Runner用于執(zhí)行Root Isolate代碼,它運(yùn)行在線程對應(yīng)平臺的線程上,屬于子線程。同時(shí),Root isolate在引擎啟動時(shí)會綁定不少Flutter需要的函數(shù)方法,這些綁定的函數(shù)可以提交渲染幀給Engine層執(zhí)行渲染操作,下圖演示了Widgets生成Layer Tree的過程。
對于每一幀,引擎通過Root Isolate通知Flutter Engine有幀需要渲染,平臺收到Flutter Engine通知后會創(chuàng)建對象和組件并生成一個(gè)Layer Tree,然后將生成的Layer Tree提交給Flutter Engine。此時(shí),只生成了需要繪制的內(nèi)容,并沒有執(zhí)行屏幕渲染,而Root Isolate就是負(fù)責(zé)將創(chuàng)建的Layer Tree繪制到屏幕上,因此如果線程過載會導(dǎo)致卡頓掉幀現(xiàn)象。
除了用于處理渲染之外,Root Isolate還需要處理來自Native Plugins的消息響應(yīng)、Timers、MicroTasks和異步IO。如果確實(shí)有無法避免的繁重計(jì)算,建議將這些耗時(shí)的操作放到獨(dú)立的Isolate去執(zhí)行,從而避免應(yīng)用UI卡頓問題。
GPU Task Runner
GPU Task Runner用于執(zhí)行設(shè)備GPU指令,UI Task Runner創(chuàng)建的Layer Tree是跨平臺的。也就是說,Layer Tree提供了繪制所需要的信息,但是由誰來完成繪制它是不關(guān)心的。
GPU Task Runner的主要責(zé)任就是負(fù)責(zé)將Layer Tree提供的信息轉(zhuǎn)化為平臺可執(zhí)行的GPU指令,同時(shí)它也負(fù)責(zé)管理每一幀繪制所需要的GPU資源,包括平臺Framebuffer的創(chuàng)建,Surface生命周期管理,以及Texture和Buffers的繪制時(shí)機(jī)等,下圖GPU Task Runner的工作流程。

UI Runner和GPU Runner運(yùn)行在不同的線程。GPU Runner會根據(jù)目前幀執(zhí)行的進(jìn)度去向UI Runner請求下一幀的數(shù)據(jù),在任務(wù)繁重的時(shí)候還可能會出現(xiàn)UI Runner的延遲任務(wù)。不過這種調(diào)度機(jī)制的好處在于,確保GPU Runner不至于過載,同時(shí)也避免了UI Runner不必要的資源消耗。
GPU Runner可以導(dǎo)致UI Runner的幀調(diào)度的延遲,GPU Runner的過載會導(dǎo)致Flutter應(yīng)用的卡頓,因此在實(shí)際使用過程中,建議為每一個(gè)Engine實(shí)例都新建一個(gè)專用的GPU Runner線程。
三、Widget、Element 和 RenderObject
要理解Flutter的渲染原理,那么就必須了解Widget、RenderObject 和 Element及其作用??偟膩碚f,F(xiàn)lutter調(diào)用runApp(rootWidget),將rootWidget傳給rootElement,做為rootElement的子節(jié)點(diǎn),生成Element樹,由Element樹生成Render樹,如下圖所示。
從上面的介紹中,我們隱約知道了Widget、RenderObject 和 Element的作用,簡單的介紹一下。
Widget:Widget 的主要作用是用來保存 Element 信息的(包括布局、渲染屬性、事件響應(yīng)等信息),本身是不可變的,Element 也是根據(jù) Widget 里面保存的配置信息來管理渲染樹,以及決定自身是否需要執(zhí)行渲染。
RenderObject:RenderObject 做為渲染樹中的對象存在,主要作用是處理布局、繪制相關(guān)的事情,而繪制的內(nèi)容是Widget傳入的內(nèi)容。
Element:Element 可以理解為是其關(guān)聯(lián)的 Widget 的實(shí)例,存放視圖構(gòu)建的上下文數(shù)據(jù),可以通過遍歷Element來查看視圖樹,Element同時(shí)持有Widget和RenderObject對象。
Flutter通過Widget樹中的每個(gè)控件創(chuàng)建不同類型的渲染對象,組成渲染對象樹,而渲染對象樹在Flutter中的展示分為四階段:布局、繪制、合成及渲染。其中,布局和繪制由RenderObject負(fù)責(zé)完成,F(xiàn)lutter采用深度優(yōu)先機(jī)制遍歷渲染樹對象,確定樹中每個(gè)對象的位置和尺寸,并把他們繪制到不同的圖層上,而合成及渲染則交給Skia完成。
下圖展示了Widget、Element 和 RenderObject的關(guān)系。
3.1 Widget
在 Flutter 中,萬物皆是 Widget,無論是可見的還是功能型的,下面是官方對Widget的介紹。
Widget 的作用是用來保存 Element 的配置信息的。
Widget 本身是不可變的。
Element 根據(jù) Widget 里面保存的配置信息來管理渲染樹。
Widget 可以多次的插入到 Widget 樹中,每插入一次,Element 都要重新裝載一遍 Widget 。
Widget 里面的 key 屬性用來決定依賴這個(gè) Widget 的 Element 在 Element 樹中是更新還是移除。
下面是Widget源碼。
abstract class Widget extends DiagnosticableTree{
const Widget({ this.key });
final Key key;
@protected
Element createElement();
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}Widget有兩個(gè)重要的方法,一個(gè)是通過 createElement 來創(chuàng)建 Element 對象的,一個(gè)是根據(jù) key 來決定更新行為的 canUpdate 方法。
3.2 RenderObject

RenderObject 是做為渲染樹中的對象存在。
RenderObject 不定義約束關(guān)系,也就是不會對子控件的布局位置、大小等進(jìn)行管理。
RenderObject 中有一個(gè) parentData 屬性,這個(gè)屬性用來保存其孩子節(jié)點(diǎn)的特定信息,如子節(jié)點(diǎn)位置,這個(gè)屬性對其孩子是透明的。
RenderObject的源碼如下。
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
ParentData parentData;
Constraints _constraints;
void layout(Constraints constraints, { bool parentUsesSize = false }) {
}
void paint(PaintingContext context, Offset offset) { }
void performLayout();
void markNeedsPaint() {
}
}
可以看出,RenderObject 的主要作用就是繪制和布局。RenderObject 在 Flutter 中的作用分為四個(gè)階段,即布局、繪制、合成和渲染。其中,布局和繪制在 RenderObject 中完成,F(xiàn)lutter 采用深度優(yōu)先機(jī)制遍歷渲染對象樹,確定樹中各個(gè)對象的位置和尺寸,并把它們繪制在不同的圖層上。繪制完畢后,合成和渲染的工作則交給 Skia 完成。
3.3 Element

Element 是關(guān)聯(lián)的Widget 的實(shí)例,并且關(guān)聯(lián)在 Widget 樹的特定位置上。
Widget 是不可變的,一個(gè) Widget 可以同時(shí)用來配置多個(gè)子 Widget 樹,而 Element 就用來代表特定位置的 Widget 。
Widget 是不可變的,而 Element 是可變的,Element決定是否需要刷新界面。
一些 Element 只能有一個(gè)子節(jié)點(diǎn),如 Container、Opacity、Center ,還有一些可以有多個(gè)子節(jié)點(diǎn),如 Column、Row 和 ListView 等。
Element 擁有自己的生命周期:
Flutter framework 通過 Widget.createElement 來創(chuàng)建一個(gè) Element 。
每當(dāng) Widget 創(chuàng)建并插入到 Widget 樹中時(shí),framework 就會通過 mount 方法來把這個(gè) widget 創(chuàng)建并關(guān)聯(lián)的 Element 插入到 Element 樹中。
通過 attachRenderObject 方法來將 render objects 來關(guān)聯(lián)到 Render 樹上,這時(shí)可以認(rèn)為 Widget 已經(jīng)顯示在屏幕上了。
每當(dāng)執(zhí)行了 rebuid 方法,Widget 代表的配置信息改變時(shí),framewrok 就會調(diào)用這個(gè)新的 Widget 的 update 方法執(zhí)行重繪。
當(dāng) Element 的祖先想要移除一個(gè)子 Element 時(shí),可以通過 deactivateChild 方法,先把這個(gè) Element 從 樹中移除,然后將這個(gè) Element 加入到一個(gè)“不活躍元素列表”中,接著 framework 就會將這個(gè) element 從屏幕移除。
總的來說,F(xiàn)lutter提出一切皆Widget,Widget 主要用來保存 Element 信息,而Element作用Widget 的實(shí)例,存放視圖構(gòu)建的上下文數(shù)據(jù),并且同時(shí)持有Widget和RenderObject對象,RenderObject的主要作用是處理布局、繪制相關(guān)的事情,確定Element樹中每個(gè)對象的位置和尺寸。
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取