flutter_runApp到挂载根节点

入口

flutter应用的入口点在main方法中调用的runApp(Widget app)方法中

widgets.binding.runApp
1
2
3
4
5
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}

这里的WidgetsFlutterBinding混入了七个 xxxbinding

  • [GestureBinding], which implements the basics of hit testing.
  • [SchedulerBinding], which introduces the concepts of frames.
  • [ServicesBinding], which provides access to the plugin subsystem.
  • [PaintingBinding], which enables decoding images.
  • [SemanticsBinding], which supports accessibility.
  • [RendererBinding], which handles the render tree.
  • [WidgetsBinding], which handles the widget tree.

并且类中只有一个ensureInitialized()方法用来初始化WidgetsBinding对象,接着去执行了scheduleAttachRootWidgetscheduleWarmUpFrame方法
ensureInitialized方法中调用WidgetsFlutterBinding进行了初始化

1
2
3
4
5
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding._instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}

scheduleAttachRootWidget

接着看 scheduleAttachRootWidget这个方法中执行了

1
2
3
Timer.run(() {
attachRootWidget(rootWidget);
});

在attachRootWidget方法中

1
2
3
4
5
6
7
8
9
10
11
12
void attachRootWidget(Widget rootWidget) {
final bool isBootstrapFrame = renderViewElement == null;
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
if (isBootstrapFrame) {
SchedulerBinding.instance.ensureVisualUpdate();
}
}

注意看这里的_renderViewElement对象是由RenderObjectToWidgetAdapter.attachToRenderTree()返回的;
在初始化RenderObjectToWidgetAdapter对象时传入了renderViewrootWidget作为参数,这里的rootWidget就是我们runApp中传入的参数;
那么这里的renderView是什么时候初始化的?我们在上面提到的WidgetsFlutterBinding混入了七个xxxbinding,这里需要了解mixin的执行顺序:
虽然首先执行的是WidgetsBindinginitInstances方法,但由于第一就执行了super.initInstances(),所以会先执行前一个RenderBindinginitInstances,然后不断super,所以最终initInstances实际的逻辑执行顺序,可以看成是从前面的Binding往后面的Binding,所以在WidgetsBindingattachRootWidget方法内renderView已经被初始化了.

RenderObjectToWidgetAdapter

继承自RenderObjectWidget,它有两个关键的抽象方法

1
2
RenderObjectElement createElement();
RenderObject createRenderObject(BuildContext context);

看下是怎么覆写的

1
2
3
4
5
@override
RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

@override
RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;

我们接着看attachToRenderTree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element!.assignOwner(owner);
});
owner.buildScope(element!, () {
element!.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element!;
}

这里传入了BuildOwner的实例owner和根元素对象.首先执行了owner.lockState,这个方法只是进行了一些断言来保证执行callback期间状态的锁定,这里callback就是4~6行代码;
在这个callback中执行了createElement(),用于创建元素,创建出来的元素也就是树的根节点;这里注意一下createElementRenderObjectToWidgetAdapter实例的方法,看下上面的方法中传入的this也就是RenderObjectToWidgetAdapter对象本身;那么在创建Element时为啥要传入Widget对象?跟踪到最父级的Element发现是为了给_widget赋值.也就是说Element持有了Widget对象,并且该元素由该组件创建

挂载

接下来是owner.buildScope,这里传入了根元素和回调函数,同样的是进行了一些断言后回调了callback,在callback中执行了元素的挂载,注意这里传入的两个参数都是null.

1
2
3
4
5
6
7
@override
void mount(Element? parent, Object? newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
assert(_child != null);
}

mount方法是RenderObjectToWidgetElement类覆写的Element中定义的方法,这里执行了父类的mount方法和_rebuild方法;
先看mount的调用路径
RootRenderObjectElement-->RenderObjectElement-->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
void mount(Element? parent, Object? newSlot) {
assert(_lifecycleState == _ElementLifecycle.initial);
assert(widget != null);
assert(_parent == null);
assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
assert(slot == null);
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
// Only assign ownership if the parent is non-null. If parent is null
// (the root node), the owner should have already been assigned.
// See RootRenderObjectElement.assignOwner().
_owner = parent.owner;
}
assert(owner != null);
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
attachNotificationTree();
}

这里维护了一些成员信息,并将树的深度_depth加1,到这里也就以为着根元素节点挂载完成
Element#mount执行完成后,回到RenderObjectToWidgetElement#mount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(() {
_debugDoingBuild = true;
return true;
}());
_renderObject = (widget as RenderObjectWidget).createRenderObject(this);
assert(!_renderObject!.debugDisposed!);
assert(() {
_debugDoingBuild = false;
return true;
}());
assert(() {
_debugUpdateRenderObjectOwner();
return true;
}());
assert(_slot == newSlot);
attachRenderObject(newSlot);
_dirty = false;
}

这里面执行了widgetcreateRenderObject(this)方法来创建_renderObject;注意一下,这里的widget其实就是根组件.也就是RenderObjectToWidgetAdapter的实例对象,调用其createRenderObject方法返回的是其实例中的container对象,也就是说Element中的_renderObject是在mount方法中通过widget.createRenderObject方法创建的

1
2
@override
RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;

这里的container对象也就是前面提到的attachRootWidget中传入的renderView对象.
对于根节点的三棵树来讲,已经完成了创建过程,单着并不代表所有的节点都是这中情况.一般情况下,组件不会持有渲染对象,只不过根组件比较特殊,需要有一个开始渲染的节点,createRenderObject方法返回的RenderView也有特殊性

总结一下

  • RenderObjectToWidgetAdapter通过构造方法持有RenderView对象
  • RenderObjectToWidgetAdapter通过createElement方法创建RenderObjectToWidgetElement对象
  • RenderObjectToWidgetElement通过mount方法持有RenderView
  • RenderObjectToWidgetElement通过构造方法(Element)持有RenderObjectToWidgetAdapter

根渲染对象的关联

挂载完了我们接着看RenderObjectElement#mount方法中调用的attachRenderObject(newSlot)

1
2
3
4
5
6
7
8
9
10
@override
void attachRenderObject(Object? newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget as ParentDataWidget<ParentData>);
}

先调用_findAncestorRenderObjectElement从元素树中向上查找第一个RenderObjectElement类型的元素节点作为先祖节点,然后调用其insertRenderObjectChild方法将自身持有的renderObject插入的渲染树中;
然后调用_findAncestorParentDataElement方法从元素树中向上查找第一个ParentDataElement<ParentData>类型的节点,如果非空,则执行_updateParentData方法;由于当前是根节点,这两个查找的方法返回的都是空

节点挂载

当父类的mount方法执行完毕后,回过头来看RenderObjectToWidgetElement#mount方法中调用的_rebuild()方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void _rebuild() {
try {
_child = updateChild(_child, (widget as RenderObjectToWidgetAdapter<T>).child, _rootChildSlot);
} catch (exception, stack) {
final FlutterErrorDetails details = FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: ErrorDescription('attaching to the render tree'),
);
FlutterError.reportError(details);
final Widget error = ErrorWidget.builder(details);
_child = updateChild(null, error, _rootChildSlot);
}
}

调用了updateChild方法,这里面有三个参数.第一个参数_child现在为null,最后一个_rootChildSlot是一个object,注意一下第二个参数widget.child:这里的widget是root也就是RenderObjectToWidgetAdapter对象的实例,它的child也就是是我们在runApp中传入的widget对象,也就是我们在前面attachRootWidget方法中创建RenderObjectToWidgetAdapter时传入的child参数.

接着看updateChild方法,我们在注释中找到了行为说明

newWidget == null newWidget != null
child == null Returns null Returns new [Element]
child != null Old child is removed, returns null Old child updated if possible, returns child or new [Element]
1
2
3
4
5
6
7
8
9
10
11
12
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
if (newWidget == null) {...}
final Element newChild;
if (child != null) {...} else {
// The [debugProfileBuildsEnabled] code for this branch is inside
// [inflateWidget], since some [Element]s call [inflateWidget] directly
// instead of going through [updateChild].
newChild = inflateWidget(newWidget, newSlot);
}
assert(...);
return newChild;
}

为了节省篇幅,这里删除了没有执行的代码;因为这里的child为空,所以会走inflateWidget(newWidget, newSlot)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Element inflateWidget(Widget newWidget, Object? newSlot) {
try {
final Key? key = newWidget.key;
if (key is GlobalKey) {
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild!;
}
}
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
} finally {
if (isTimelineTracked)
Timeline.finishSync();
}
}

这里检查了组件是否有key并且key是不是GlobalKey.这里先放一下
后面调用newWidget.createElement()创建了element,并且调用其mount进行挂载.
然后就开始了树的遍历进行挂载,根据我们在runApp中传入的组件不同,调用不同对象的方法,


flutter_runApp到挂载根节点
https://blog.huangyuanlove.com/2023/01/18/flutter-runApp到挂载根节点/
作者
HuangYuan_xuan
发布于
2023年1月18日
许可协议
BY HUANG兄