一,概览
这块的内容比较多且有些复杂,为了不让大家迷失在源码的海洋里,我们还是举个例子先简单了解一下这个体系。
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框架是怎么运行的。
