Flutter框架分析分析系列文章:
《Flutter框架分析(三)-- Widget,Element和RenderObject》
《Flutter框架分析(四)-- Flutter框架的執行》
前言
前面兩篇Flutter框架分析的文章介紹了渲染流水線,window和框架的初始化。這篇文章繼續來理一下對Flutter app開發者來說比較重要的Widget
,Element
和RenderObject
體系。Flutter的理念是一切都是Widget
(Everythin is Widget)。開發者在開發Flutter app的時候主要都是在寫很多Widget
。那麼這三者之間是什麼關係?它們是怎麼工作的呢?讓我們來一探究竟。
概覽
這塊的內容比較多且有些複雜,為了不讓大家迷失在原始碼的海洋裡,我們還是舉個例子先簡單瞭解一下這個體系。
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框架是怎麼執行的。