盒子
盒子
文章目录
  1. runApp
  2. Element.mount
    1. 1.RenderObjectElement会创建RenderObject
    2. 2.创建子Element并mount到Element树
  3. mount流程解析

Flutter三棵树创建原理

flutter的渲染机制基本就是靠Widget、Element、RenderObject三棵树去实现的,这篇博客就来讲讲这三棵树是怎么创建的。

首先我们来看看这三者到底是个啥:

  • Widget: 描述一个UI元素的配置数据,不可变,修改信息需要重新new

  • Element: 通过Widget配置实例化出来的对象,它是可变的

  • 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!;
}
  1. 创建RenderObjectToWidgetAdapter作为Widget树的根,将传入的Widget挂上去
  2. 调用RenderObjectToWidgetAdapter.createElement创建Element
  3. 调用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) {
...
// 插入RenderObject树
_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
// 子widget是child参数传进去的
RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
)


void mount(Element? parent, Object? newSlot) {
...
_rebuild();
...
}

void _rebuild() {
...
// widget.child拿到构造函数传进去的子widget,即rootWidget
_child = updateChild(_child, widget.child, _rootChildSlot);
...
}

Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
...
newChild = inflateWidget(newWidget, newSlot);
...
}

Element inflateWidget(Widget newWidget, Object? newSlot) {
...
// 创建子Element
final Element newChild = newWidget.createElement();
...
// 调用子Element的mount方法将它挂到Element树上,parent是第一个参数this
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();
//updateChild在上面也有追踪这里就不列出来了,内部调用了built.createElement创建子Element并返回
_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是一个递归的过程,总结下来有下面几个步骤

  1. Element如果是RenderObjectElement则创建RenderObject,并从祖先找到上一个RenderObjectElement,然后调用祖先RenderObjectElement的RenderObject的insertRenderObjectChild方法插入创建的RenderObject
  2. 如果子widget需要build出来就调用build方法创建子widget,如果不需要直接在成员变量可以拿到子widget
  3. 调用子widget的createElement创建子Element
  4. 调用子Element的mount方法将子Element的parent设置成自己,然后子Element去到第1步

下面的动图展示了整个流程:

或者可以下载PPT查看