Flutter三棵树创建原理
2021.07.13
林嘉伟
flutter的渲染机制基本就是靠Widget、Element、RenderObject三棵树去实现的,这篇博客就来讲讲这三棵树是怎么创建的。
首先我们来看看这三者到底是个啥:
让我们用一个简单的demo来做讲解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void main() { runApp(MyApp()); }
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: HelloWorldPage(), ); } }
class HelloWorldPage extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: Text("Hello World", style: TextStyle(color: Colors.blue)), ); } }
|
上面的代码正在屏幕的中间显示了一个Hello World字符串。
runApp
在main函数里面只有一行runApp调用,追踪下去我们可以看到它主要做了三件事情:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..scheduleAttachRootWidget(app) ..scheduleWarmUpFrame(); }
void scheduleAttachRootWidget(Widget rootWidget) { Timer.run(() { attachRootWidget(rootWidget); }); } void attachRootWidget(Widget rootWidget) { ... _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget, ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?); ... }
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) { ... element = createElement(); ... element!.mount(null, null); ... return element!; }
|
- 创建RenderObjectToWidgetAdapter作为Widget树的根,将传入的Widget挂上去
- 调用RenderObjectToWidgetAdapter.createElement创建Element
- 调用Element.mount将它挂到Element树上,Element树的根节点的parent为null
Element.mount
Element的mount方法是三棵树创建流程的关键步骤,不同类型的Element mount的流程不太一样。
1.RenderObjectElement会创建RenderObject
如果Element是RenderObjectElement类型的,那么它对应的Widget一定是RenderObjectWidget类型的,这是它的构造函数决定的:
1 2 3 4
| abstract class RenderObjectElement extends Element { RenderObjectElement(RenderObjectWidget widget) : super(widget); ... }
|
它在mount的时候会调用RenderObjectWidget.createRenderObject创建RenderObject然后将它挂到RenderObject树上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| RenderObject get renderObject => _renderObject!;
RenderObject? _renderObject;
void mount(Element? parent, Object? newSlot) { ... _renderObject = widget.createRenderObject(this); ... attachRenderObject(newSlot); ... }
void attachRenderObject(Object? newSlot) { ... _ancestorRenderObjectElement = _findAncestorRenderObjectElement(); _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot); ... }
|
这个_findAncestorRenderObjectElement方法比较魔性,找的是祖先RenderObjectElement,其实就是往parent一层层查找,直到找的RenderObjectElement:
1 2 3 4 5 6
| RenderObjectElement? _findAncestorRenderObjectElement() { Element? ancestor = _parent; while (ancestor != null && ancestor is! RenderObjectElement) ancestor = ancestor._parent; return ancestor as RenderObjectElement?; }
|
insertRenderObjectChild方法将创建的RenderObject插入成为祖先RenderObjectElement的RenderObject的子节点,这样就把创建的RenderObject挂到了RenderObject树上。
2.创建子Element并mount到Element树
处理完本节点的RenderObject之后,就会创建子Element将它的parent设置成自己,mount到Element树上。
Element都是通过Widget.createElement创建的,而Element会保存创建它的Widget。所以可以通过这个Widget去获取子Widget,然后用子Widget去创建子Element。
子Widget的获取有两种方式,如果是在Widget的构造函数传入的,那么直接可以拿到它,例如上面的RenderObjectToWidgetAdapter,然后用它去createElement创建子Element:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget, )
void mount(Element? parent, Object? newSlot) { ... _rebuild(); ... }
void _rebuild() { ... _child = updateChild(_child, widget.child, _rootChildSlot); ... }
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { ... newChild = inflateWidget(newWidget, newSlot); ... }
Element inflateWidget(Widget newWidget, Object? newSlot) { ... final Element newChild = newWidget.createElement(); ... newChild.mount(this, newSlot); ... return newChild; }
|
像StatelessWidget这种子widget是build出来的,则在mount的时候会调用它的build方法创建子widget,然后用它去createElement创建子Element:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void mount(Element? parent, Object? newSlot) { ... _firstBuild(); ... }
void _firstBuild() { rebuild(); }
void rebuild() { ... performRebuild(); ... }
void performRebuild() { ... built = build(); _child = updateChild(_child, built, slot); ... }
Widget build() => widget.build(this);
|
最终得到的三棵树大概长下面的样子,由于没有分成所以看上去是链表而不是树,但是这不影响我们理解,一旦某些节点有多个child节点就是输了:
Element通过widget成员持有Widget,如果是RenderObjectElement还通过renderObject成员持有RenderObject,可以看出来Element是连接Widget和RenderObject的桥梁。三个树的构建也都是通过递归mount Element去实现的。
当RenderObject树创建出来之后,Flutter的引擎就能遍历它去执行绘制将画面渲染出来了。
mount流程解析
从上面的代码可以看得出来,mount是一个递归的过程,总结下来有下面几个步骤
- Element如果是RenderObjectElement则创建RenderObject,并从祖先找到上一个RenderObjectElement,然后调用祖先RenderObjectElement的RenderObject的insertRenderObjectChild方法插入创建的RenderObject
- 如果子widget需要build出来就调用build方法创建子widget,如果不需要直接在成员变量可以拿到子widget
- 调用子widget的createElement创建子Element
- 调用子Element的mount方法将子Element的parent设置成自己,然后子Element去到第1步
下面的动图展示了整个流程:
或者可以下载PPT查看