前言
前面两篇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框架是怎么运行的。