Flutter 源码阅读 - 三棵树流程分析(二)
一、前言
由于 Widget
分类可以分为组合型 Widget
和 RenderObjectWidget两大类以及子类别
,它们分别对应不同的 Element
分类,所以文章将会按照 Widget
的分类用几个小的案例来进行分析。
二、RenderObjectWidget 案例
下面我们先通过一个简单的案例来进行分析,在 runApp
中直接渲染一个 ColoredBox
,代码如下:
void main() {
runApp(
const ColoredBox(color: Colors.blue),
);
}
程序运行起来后,我们看到如下的效果:
此时我们从程序入口 runApp
开始分析,源码如下:
// flutter/lib/src/widgets/binding.dart
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding._instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}
它接收一个 Widget
类型的参数,接下来执行WidgetsFlutterBinding.ensureInitialized()
方法,方法体内首先会判断WidgetsBinding._instance
是否为 null
,若是,则会调用 WidgetsFlutterBinding
构造函数来进行初始化,最后返回 instance
实例。此时我们意外的发现 WidgetsFlutterBinding
根本就没有构造函数,那么此时就会执行父类的构造函数,下面我们看下 BindingBase
的源码。
可以看到构造函数中有个 initInstances
方法,此时我们打开断点调试跟踪一下。
通过断点调试以及发现,它的调用堆栈如下图:
在 ensureInitialized
方法中,WidgetsBinding._instance
为 null
,然后执行 WidgetsFlutterBinding
构造方法,此时它并没有构造方法,便会执行 BindingBase
构造函数,然后执行 initInstances
方法,大家在这里要注意下,由于 GestureBinding
,SchedulerBinding
, ServicesBinding
, PaintingBinding
, SemanticsBinding
,RendererBinding
, WidgetsBinding
这七大 binding 都是混入了 BindingBase
,而且每个 binding 都重写了 initInstances
,并且在内部调用了 super.initInstances()
,所以它们七大 bingding 中 initInstances
的执行顺序为:WidgetsBinding -> RendererBinding -> SemanticsBinding -> PaintingBinding -> ServicesBinding -> SchedulerBinding -> GestureBinding
,最后执行 BindingBase
中的 initInstances
函数,在执行的过程中把当前 this
赋值给 _instance
保存。
这里或许大家会问,initInstances
执行顺序为什么是这样的?其实是 dart 中 mixin 之线性化了。由于七大 binding 都会混入 BindingBase,而且 WidgetsFlutterBinding 继承了 BindingBase 并且依次 with 了这七大 binding。那么这里就产生了一个问题,如果with
后的多个类中有相同的方法,那么当调用该方法时,会调用哪个类里的方法?由于距离with
关键字越远的类会重写前面类中的相同方法,因此分为以下两种情况:
- 如果当前使用类重写了该方法,就会调用当前类中的方法。
- 如果当前使用类没有重写了该方法,则会调用距离
with
关键字最远类中的方法
上面已经提到了每个 binding 都重写了 initInstances
,并且在内部调用了 super.initInstances()
,所以执行顺序就是这样的了。
下面我们看个小例子:
abstract class BindingBase {
BindingBase() {
initInstances();
}
void initInstances() {
print("BindingBase-->initInstances");
}
}
mixin GestureBinding on BindingBase {
@override
void initInstances() {
print("GestureBinding-->initInstances");
super.initInstances();
}
}
mixin RendererBinding on BindingBase {
@override
void initInstances() {
print("RendererBinding-->initInstances");
super.initInstances();
}
}
mixin WidgetsBinding on BindingBase {
@override
void initInstances() {
print("WidgetsBinding-->initInstances");
print('super.runtimeType--> ${super.runtimeType}');
super.initInstances();
}
}
mixin PaintingBinding on BindingBase {
@override
void initInstances() {
print("PaintingBinding-->initInstances");
super.initInstances();
}
}
class WidgetsFlutterBinding extends BindingBase
with GestureBinding, PaintingBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
return WidgetsFlutterBinding();
}
}
main(List<String> arguments) {
WidgetsFlutterBinding();
}
输出结果如下:
我们在 WidgetsBinding
、RendererBinding
、PaintingBinding
、GestureBinding
中都调用了 super.initInstances
方法,也因此会一级一级往上调用。如果我们删除 WidgetsBinding 中的 super.initInstances
方法,则会终止调用 initInstances
。
mixin WidgetsBinding on BindingBase {
@override
void initInstances() {
print("WidgetsBinding-->initInstances");
print('super.runtimeType --> ${super.runtimeType}');
// super.initInstances(); // 注释掉此句代码
}
}
输出结果如下:
从上面改的分析我们可以得出结论:ensureInitialized
只是实例化了 WidgetsFlutterBinding
。 此时会继续执行 scheduleAttachRootWidget
方法 ,该方法接收一个 Widget
,然后继续执行 attachRootWidget
,下面我们来看下 attachRootWidget
方法,做了什么事?
Takes a widget and attaches it to the [renderViewElement], creating it if necessary. This is called by [runApp] to configure the widget tree.
通过官方注释可以看出,如果有必要的话,获取一个小部件并将其附加到 renderViewElement
上。
下面着重分析下红框中的代码,首先执行 RenderObjectToWidgetAdapter
构造方法,它需要三个入参,debugShortDescription
是调试用的,这里就不做解释,child
就是我们在 runApp
中所写的 ColoredBox
。
那 container
又是什么呢,从断点中可以看出它是 RenderView
?它其实就是我们刚才所讲的 RendererBinding
中 initInstances
所初始化的,这里首先初始化了 PipelineOwner
,然后执行 initRenderView
对 renderView
进行赋值,然后通过 getter
获取到 renderView。
到此我们已经完全明白了renderView
和rootWidget
的来历,下面我们继续分析 attachToRenderTree
方法,它接收两个参数 BuildOwner
和可选参数 RenderObjectToWidgetElement<T>?
,此时的 buildOwner
就是在 WidgetsBinding
的 initInstances
进行初始化的,renderViewElement
此时为 null
。
我们来一起看一下 attachToRenderTree
的源码,重点分析一下红框里部分的代码,如下图所示:
此时,element
为 null
,所以会走红框里的逻辑,首先我们来看下 owner.lockState
,它接受一个 callback
,其他的逻辑就是进行一些断言判断,最后执行 callback
方法,也就是会执行 element = createElement(),element!.assignOwner(owner)
这两个方法。
我们回过头来看下 createElement
,接收一个 this
, 也就是 RenderObjectToWidgetAdapter
这个 Widget
,然后执行 RenderObjectToWidgetElement
构造方法,接着执行 super
函数(也就是 RootRenderObjectElement
构造函数),把 widget
一路传递下去,以此类推执行 RenderObjectElement
构造方法,最后执行 Element
构造方法。在 Element
构造方法中 把 RenderObjectToWidgetAdapter
赋值给 _widget
,可以看出 Element
便持有了 Widget
,也就是 attachToRenderTree
的返回值,这里可以得知根组件 createElement
的触发时机。
接着会执行 assignOwner
方法,这里我们先不做讲解。接着来看 owner.buildScope
,通过源码可以看出在执行 callback
之前也都是进行一些断言处理,最后会执行 callback
,也就是 buildScope
中传递的回调方法。
因为此时的 element
为 RenderObjectToWidgetElement
,所以会执行 RenderObjectToWidgetElement
的 mount
方法。
接着会执行一系列父级的 super.mount()
方法,一直到执行 Element
的 mount
方法,在它的方法中主要对一些属性的赋值操作,当 Element
的 mount
方法 执行完后便会出栈,接着执行 RenderObjectElement
的 mount
方法,此时会执行 widget
的 createRenderObject
方法,通过断点调试我们可以看到 widget
就是根组件 RenderObjectToWidgetAdapter
。
接下来执行 RenderObjectToWidgetAdapter
的 createRenderObject
方法,会返回 Renderview
,也就是在调用 attachRootWidget
方法中 RenderObjectToWidgetAdapter
构造函数所传递进来的 renderView
。
此时返回值赋值给 _renderObject
,我们可以得出,RenderObjectElement
会持有 RenderObject
对象。
接下来会执行 RenderObjectElement
中的 attachRenderObject
方法,通过源码可以看出,此方法做了三件事:
- 通过
_findAncestorRenderObjectElement
方法,寻找首位RenderObjectElement
类型的节点,并且赋值给_ancestorRenderObjectElement
; - 调用
_ancestorRenderObjectElement
的insertRenderObjectChild
方法将renderObject
插入渲染树中; - 通过
_findAncestorParentDataElement
方法找到首位ParentDataElement<ParentData>
类型的节点。
由于此时 ancestor
为 null
, _findAncestorRenderObjectElement
相当于执行了个寂寞,此时的 _ancestorRenderObjectElement
为 null
,_ancestorRenderObjectElement
也不会执行,_findAncestorParentDataElement
也相当于执行了个寂寞,整体下来,attachRenderObject
相当于没有执行。
此时会继续出栈,然后继续执行 RenderObjectToWidgetElement
中的 mount
方法,执行根组件中的 _rebuild
方法,在 rebuild
方法中,主要会执行 updateChild
方法,第一个参数 _child
为 null
,第二个参数就是我们在 runApp
中传入的 ColoredBox
,第三个参数为 Object
。
紧接着会执行 Element
中的 updateChild
方法,因为此时 的 child
为 null
,所以会执行 inflateWidget
方法。
从断点中可以看出传入 inflateWidget
中的 newWidget
为 ColoredBox
,我们来看下 inflateWidget
做了哪些事情:
- 校验
key
是否为GlobalKey
; newWidget
执行createElement
;- 执行
newWidget
创建createElement
返回值的mount
方法。
看到这里是否有似曾相识的感觉,然后会走入下一个循环,执行 createElement
,执行 Element
的 mount
方法,执行 createRenderObject
方法。
在第二件事情中会执行 createElement
方法,此时的 this
为 ColoredBox
,因为 ColoredBox
继承了 SingleChildRenderObjectWidget
,所以此时会执行它里面的 createElement
方法创建一个 SingleChildRenderObjectElement
,然后继续执行 super
方法,一直到 Element
构造方法执行完毕。
第三件事情中执行 SingleChildRenderObjectElement
中的 mount
方法,然后再执行 Element
中的 updateChild
方法。
三、总结
至此,我们已经走完了 ColoredBox
(RenderObjectWidget
) 的流程,其中有 mount
的执行时机、Element
的创建以及 RenderObject
的创建等等。由于篇幅的限制,在后续系列文章中将会讲解 StatelessWidget
案例以及 StatefulWidget
案例。
转载自:https://juejin.cn/post/7171371644735193102