一,概覽
這塊的內容比較多且有些復雜,為了不讓大家迷失在源碼的海洋里,我們還是舉個例子先簡單了解一下這個體系。
void main() { runApp(MyWidget()); } class MyWidget extends StatelessWidget { final String _message = "Flutter框架分析"; @override Widget build(BuildContext context) => ErrorWidget(_message); }
這個例子的利用Flutter自帶的ErrorWidget
顯示我們自定義的一句話:“Flutter框架分析”。沒錯,這個ErrorWidget
就是當你的代碼出bug的時候顯示在屏幕上的可怕的紅底黃字信息。放張截屏大家感受一下。


這里使用它是因為它是最簡單,層級最少的一個Widget
。以方便我們理解Flutter框架,避免被MaterialApp
那深不可測的element tree和render tree勸退。
運行上述例子以后再打開Flutter Inspector看一下:

從上圖可見就三個層級 root->
MyWidget
->
ErrorWidget
。這看起來是個widget tree。這里的root對應的是上篇文章里說的
RenderObjectToWidgetAdapter
。但這實際上是這樣的一個
element tree:
RenderObjectToWidgetElement
->
StatelessElement
->
LeafRenderObjectElement
。還記得我們上篇文章里說的,
RenderObjectToWidgetElement
是
element tree的根節點。看看圖中上方紅框,這個根節點是持有
render tree的根節點
RenderView
的。它的子節點就是我們自己寫的
MyWidget
對應的
StatelessElement
。而這個
element是不持有
RenderObject
的。只有最下面的
ErrorWidget
對應的
LeafRenderObjectElement
才持有第二個
RenderObject
。所以
render tree是只有兩層的:
RenderView
->
RenderErrorBox
。以上所說用圖來表示就是這樣的:

圖中綠色連接線表示的是element tree的層級關系。黃色的連接線表示render tree的層級關系。
從上面這個例子可以看出來,Widget
是用來描述對應的Element
的描述或配置。Element
組成了element tree,Element
的主要功能就是維護這棵樹,節點的增加,刪除,更新,樹的遍歷都在這里完成。Element
都是從Widget
中生成的。每個Widget
都會對應一個Element
。但是並非每個Widget
/Element
會對應一個RenderObject
。只有這個Widget
繼承自RenderObjectWidget
的時候才會有對應的RenderObject
。
總的來說就是以下幾點:
Widget
是對Element
的配置或描述。Flutter app開發者主要的工作都是在和Widget
打交道。我們不需要關心樹的維護更新,只需要專注於對Widget
狀態的維護就可以了,大大減輕了開發者的負擔。Element
負責維護element tree。Element
不會去管具體的顏色,字體大小,顯示內容等等這些UI的配置或描述,也不會去管布局,繪制這些事,它只管自己的那棵樹。Element
的主要工作都處於渲染流水線的構建(build)階段。RenderObject
負責具體布局,繪制這些事情。也就是渲染流水線的布局(layout)和 繪制(paint)階段。
二,下Widget
,Element
和RenderObject
-
Widget
基類Widget
很簡單
@immutable abstract class Widget extends DiagnosticableTree { const Widget({ this.key }); ... @protected Element createElement(); ... }
方法createElement()
負責實例化對應的Element
。由其子類實現。接下來看下幾個比較重要的子類:
-
StatelessWidget
abstract class StatelessWidget extends Widget { /// Initializes [key] for subclasses. const StatelessWidget({ Key key }) : super(key: key); @override StatelessElement createElement() => StatelessElement(this); @protected Widget build(BuildContext context); }
StatelessWidget
對Flutter開發者來講再熟悉不過了。它的createElement
方法返回的是一個StatelessElement
實例。
StatelessWidget
沒有生成RenderObject
的方法。所以StatelessWidget
只是個中間層,它需要實現build
方法來返回子Widget
。
-
StatefulWidget
abstract class StatefulWidget extends Widget { @override StatefulElement createElement() => StatefulElement(this); @protected State createState(); }
StatefulWidget
對Flutter開發者來講非常熟悉了。createElement
方法返回的是一個StatefulElement
實例。方法createState()
構建對應於這個StatefulWidget
的State
。
StatefulWidget
沒有生成RenderObject
的方法。所以StatefulWidget
也只是個中間層,它需要對應的State
實現build
方法來返回子Widget
。
-
State
說到StatefulWidget
就不能不說說State
。
abstract class State<T extends StatefulWidget> extends Diagnosticable { T get widget => _widget; T _widget; BuildContext get context => _element; StatefulElement _element; bool get mounted => _element != null; void initState() { } void didUpdateWidget(covariant T oldWidget) { } void setState(VoidCallback fn) { final dynamic result = fn() as dynamic; _element.markNeedsBuild(); } void deactivate() { } void dispose() { } Widget build(BuildContext context); void didChangeDependencies() { } }
從源碼可見,State
持有對應的Widget
和Element
。注意這一句BuildContext get context => _element;
。我們在調用build
時候的入參BuildContex
其實返回的就是Element
。
mounted
,用來判斷這個State
是不是關聯到element tree中的某個Element
。如果當前State
不是在mounted == true
的狀態,你去調用setState()
是會crash的。
-
- 函數
initState()
用來初始化State
。 - 函數
didUpdateWidget(covariant T oldWidget)
在這個State
被換了個新的Widget
以后被調用到。是的,State
對應的Widget
實例只要是相同類型的是可以被換來換去的。 - 函數
setState()
我們很熟悉了。這個函數只是簡單執行傳入的回調然后調用_element.markNeedsBuild()
。你看,如果此時_element
為空的時候會不會出問題?所以建議大家在調用setState()
之前用mounted
判斷一下。另外要注意的一點是,這個函數也是觸發渲染流水線的一個點。后續我會在另外的文章里從這個點出發,給大家說說渲染流水線如何在Widget
、Element
和RenderObject
架構下運行。 - 函數
deactivate()
在State
對應的Element
被從樹中移除后調用,這個移除可能是暫時移除。 - 函數
dispose()
在State
對應的Element
被從樹中移除后調用,這個移除是永久移除。 - 函數
build(BuildContext context)
,大家很熟悉了,不多說了。 - 函數
didChangeDependencies()
,State
的依賴發生變化的時候被調用,具體什么樣的依賴后文再說。
- 函數
StatefullWidget
和State
對Flutter app開發者來說可能會是打交道最多的。有些細節還需要結合Element
做深入的理解。
-
InheritedWidget
InheritedWidget既不是StatefullWidget也不是StatelessWidget。它是用來向下傳遞數據的。在InheritedWidget之下的子節點都可以通過調用BuildContext.inheritFromWidgetOfExactType()來獲取這個InheritedWidget。它的createElement()函數返回的是一個InheritedElement。 abstract class InheritedWidget extends ProxyWidget { const InheritedWidget({ Key key, Widget child }) : super(key: key, child: child); @override InheritedElement createElement() => InheritedElement(this); @protected bool updateShouldNotify(covariant InheritedWidget oldWidget); }
-
RenderObjectWidget
RenderObjectWidget用來配置RenderObject。其createElement()函數返回RenderObjectElement。由其子類實現。相對於上面說的其他Widget。這里多了一個createRenderObject()方法。用來實例化RenderObject。 abstract class RenderObjectWidget extends Widget { const RenderObjectWidget({ Key key }) : super(key: key); @override RenderObjectElement createElement(); @protected RenderObject createRenderObject(BuildContext context); @protected void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { } @protected void didUnmountRenderObject(covariant RenderObject renderObject) { } }
RenderObjectWidget
只是個配置,當配置發生變化需要應用到現有的RenderObject
上的時候,Flutter框架會調用updateRenderObject()
來把新的配置設置給相應的RenderObject
。
RenderObjectWidget
有三個比較重要的子類:
-
LeafRenderObjectWidget
這個Widget
配置的節點處於樹的最底層,它是沒有孩子的。對應LeafRenderObjectElement
。SingleChildRenderObjectWidget
,只含有一個孩子。對應SingleChildRenderObjectElement
。MultiChildRenderObjectWidget
,有多個孩子。對應MultiChildRenderObjectElement
。
-
Element
Element
構成了element tree。這個類主要在做的事情就是維護這棵樹。從上面對Widget
的分析我們可以看出,好像每個特別的Widget
都會有一個對應的Element
。特別是對於RenderObjectWidget
。如果我有一個XXXRenderObjectWidget
,它的createElement()
通常會返回一個XXXRenderObjectElement
。為簡單起見。我們的分析就僅限於比較基礎的一些Element
。
首先來看一下基類Element
。
abstract class Element extends DiagnosticableTree implements BuildContext { Element _parent; Widget _widget; BuildOwner _owner; dynamic _slot; void visitChildren(ElementVisitor visitor) { } Element updateChild(Element child, Widget newWidget, dynamic newSlot) { } void mount(Element parent, dynamic newSlot) { } void unmount() { } void update(covariant Widget newWidget) { } @protected Element inflateWidget(Widget newWidget, dynamic newSlot) { ... final Element newChild = newWidget.createElement(); newChild.mount(this, newSlot); return newChild; } void markNeedsBuild() { if (dirty) return; _dirty = true; owner.scheduleBuildFor(this); } void rebuild() { if (!_active || !_dirty) return; performRebuild(); } @protected void performRebuild(); }
Element
持有當前的Widget
,一個BuildOwner
。這個BuildOwner
是之前在WidgetsBinding
里實例化的。Element
是樹結構,它會持有父節點_parent
。_slot
由父Element
設置,目的是告訴當前Element
在父節點的什么位置。由於Element
基類不知道子類會如何管理孩子節點。所以函數visitChildren()
由子類實現以遍歷孩子節點。
函數updateChild()
比較重要,用來更新一個孩子節點。更新有四種情況:
-
- 新
Widget
為空,老Widget
也為空。則啥也不做。 - 新
Widget
為空,老Widget
不為空。這個Element
被移除。 - 新
Widget
不為空,老Widget
為空。則調用inflateWidget()
以這個Wiget
為配置實例化一個Element
。 - 新
Widget
不為空,老Widget
不為空。調用update()
函數更新子Element
。update()
函數由子類實現。
- 新
新Element
被實例化以后會調用mount()
來把自己加入element tree。要移除的時候會調用unmount()
。
函數markNeedsBuild()
用來標記Element
為“臟”(dirty)狀態。表明渲染下一幀的時候這個Element
需要被重建。
函數rebuild()
在渲染流水線的構建(build)階段被調用。具體的重建在函數performRebuild()
中,由Element
子類實現。
Widget
有一些比較重要的子類,對應的Element
也有一些比較重要的子類。
ComponentElement ComponentElement表示當前這個Element是用來組合其他Element的。 abstract class ComponentElement extends Element { ComponentElement(Widget widget) : super(widget); Element _child; @override void performRebuild() { Widget built; built = build(); _child = updateChild(_child, built, slot); } Widget build(); }
ComponentElement
繼承自Element
。是個抽象類。_child
是其孩子。在函數performRebuild()
中會調用build()
來實例化一個Widget
。build()
函數由其子類實現。
StatelessElement StatelessElement對應的Widget是我們熟悉的StatelessWidget。 class StatelessElement extends ComponentElement { @override Widget build() => widget.build(this); @override void update(StatelessWidget newWidget) { super.update(newWidget); _dirty = true; rebuild(); } }
其build()
函數直接調用的就是StatelessWidget.build()
。現在你知道你寫在StatelessWidget
里的build()
是在哪里被調用的了吧。而且你看,build()
函數的入參是this
。我們都知道這個函數的入參應該是BuildContext
類型的。這個入參其實就是這個StatelessElement
。
-
StatefulElement
StatefulElement
對應的Widget
是我們熟悉的StatefulWidget
。
class StatefulElement extends ComponentElement { /// Creates an element that uses the given widget as its configuration. StatefulElement(StatefulWidget widget) : _state = widget.createState(), super(widget) { _state._element = this; _state._widget = widget; } @override Widget build() => state.build(this); @override void _firstBuild() { final dynamic debugCheckForReturnedFuture = _state.initState() _state.didChangeDependencies(); super._firstBuild(); } @override void deactivate() { _state.deactivate(); super.deactivate(); } @override void unmount() { super.unmount(); _state.dispose(); _state._element = null; _state = null; } @override void didChangeDependencies() { super.didChangeDependencies(); _state.didChangeDependencies(); } }
在StatefulElement
構造的時候會調用對應StatefulWidget
的createState()
函數。也就是說State
是在實例化StatefulElement
的時候被實例化的。並且State
實例會被這個StatefulElement
實例持有。從這里也可以看出為什么StatefulWidget
的狀態要由單獨的State
管理,每次刷新的時候可能會有一個新的StatefulWidget
被創建,但是State
實例是不變的。
build()
函數調用的是我們熟悉的State.build(this)
,現在你也知道了State
的build()
函數是在哪里被調用的了吧。而且你看,build()
函數的入參是this
。我們都知道這個函數的入參應該是BuildContext
類型的。這個入參其實就是這個StatefulElement
。
我們都知道State有狀態,當狀態改變時對應的回調函數會被調用。這些回調函數其實都是在StatefulElement
里被調用的。
-
- 在函數
_firstBuild()
里會調用State.initState()
和State.didChangeDependencies()
。 - 在函數
deactivate()
里會調用State.deactivate()
。 - 在函數
unmount()
里會調用State.dispose()
。 - 在函數
didChangeDependencies()
里會調用State.didChangeDependencies()
。
- 在函數
-
InheritedElement
InheritedElement
對應的Widget
是InheritedWidget
。其內部實現主要是在維護對其有依賴的子Element
的Map
,以及在需要的時候調用子Element
對應的didChangeDependencies()
回調,這里就不貼代碼了,大家感興趣的話可以自己去看一下源碼。
-
RenderObjectElement
RenderObjectElement對應的Widget是RenderObjectWidget。 abstract class RenderObjectElement extends Element { RenderObject _renderObject; @override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); _renderObject = widget.createRenderObject(this); attachRenderObject(newSlot); _dirty = false; } @override void unmount() { super.unmount(); widget.didUnmountRenderObject(renderObject); } @override void update(covariant RenderObjectWidget newWidget) { super.update(newWidget); widget.updateRenderObject(this, renderObject); _dirty = false; } @override void performRebuild() { widget.updateRenderObject(this, renderObject); _dirty = false; } @protected void insertChildRenderObject(covariant RenderObject child, covariant dynamic slot); @protected void moveChildRenderObject(covariant RenderObject child, covariant dynamic slot); @protected void removeChildRenderObject(covariant RenderObject child); }
-
- 函數
mount()
被調用的時候會調用RenderObjectWidget.createRenderObject()
來實例化RenderObject
。 - 函數
update()
和performRebuild()
被調用的時候會調用RenderObjectWidget.updateRenderObject()
。 - 函數
unmount()
被調用的時候會調用RenderObjectWidget.didUnmountRenderObject()
。
- 函數
-
RenderObject
RenderObject
負責渲染流水線布局(layout)階段和繪制(paint)階段的工作。同時也維護render tree。對render tree的維護方法是來自基類AbstractNode
。這里我們主要關注和渲染流水線相關的一些方法。
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget { void markNeedsLayout() { ... } void markNeedsPaint() { ... } void layout(Constraints constraints, { bool parentUsesSize = false }) { ... if (sizedByParent) { performResize(); } ... performLayout(); ... } void performResize(); void performLayout(); void paint(PaintingContext context, Offset offset) { } }
markNeedsLayout()
標記這個RenderObject
需要重新做布局。markNeedsPaint
標記這個RenderObject
需要重繪。
這兩個函數只做標記。標記之后Flutter框架會調度一幀,在下一個Vsync信號到來之后才真正做布局和繪制。
真正的布局在函數layout()
中進行。這個函數會做一次判斷,如果sizedByParent
為true
。則會調用performResize()
。表明這個RenderObject
的尺寸僅由其父節點決定。然后會調用performLayout()
做布局。performResize()
和performLayout()
都需要RenderObject
的子類去實現。`
總結
Widget
,Element
和RenderObject
體系是Flutter框架的核心。其中Element
需要好好理解。Flutter的渲染流水線中的構建(build)階段主要就是在維護更新element tree里面的Element
節點。只有理解了Element
和element tree,才是真正掌握了Flutter框架。這篇文章里只是一些靜態的說明。下篇文章我會嘗試從渲染流水線動態運行的角度分析一下Flutter框架是怎么運行的。