Flutter Framework 渲染流程分析(二):Element
本文是
Flutter Framework 渲染流程分析中的第二篇
- Flutter Framework 渲染流程分析(一):开篇
- Flutter Framework 渲染流程分析(二):Element
- Flutter Framework 渲染流程分析(三):RenderObject
- Flutter Framework 渲染流程分析(四):Layer
- Flutter Framework 渲染流程分析(五):常见问题分析
上篇文章讲了Widget和Element中一些关键的属性和方法,粗略聊了一下Element中方法调用的流程。用一张图表示
怎么形成一棵Element Tree?思路其实很简单,Element在挂载自身的时候,生成childElement并让childElement挂载到自己节点下面,childElement挂载时又会触发child节点同样的处理。下面将就通过源码一步步去分析,注意在所有章节中assert或者一些无关紧要的代码都会用...省略掉
mount()
Element底下有两个直接子类ComponentElement和RenderObjectElement,因为在方法实现和调用上存在些许差异,为了方便理解,我们取稍简单些的ComponentElement去分析。按照上述的流程图,先看mount的处理
// code 2.1
// Component
void mount(Elment? parent, Object? newSlot) {
super.mount(parent, newSlot); // code 2.2
...
_firstBuild();
...
}
// code 2.2
// Element
void mount(Element? parent, Object? newSlot) {
...
// 更新一些节点信息
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
_owner = parent.owner;
}
...
// 如果是 GlobalKey,会存到 owner 上,以便后续复用
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
...
}
code 2.1 的代码很简单,在调用父类的mount方法进行一些必要的更新之外就执行_firstBuild()
_firstBuild()
// code 2.3
// ComponentElement
void _firstBuild() {
rebuild();
}
ComponentElement 没有重写rebuild() 方法,所以要看父类的处理
rebuild()
// code 2.4
// Element
void rebuild({bool force = false}) {
// 对状态进等一些判断,减少一些不必要的 build 行为
if (_lifecycleState != _ElementLifecycle.active || (!_dirty && !_force)) {
return;
}
...
try {
performRebuild();
} finally {
...
}
}
rebuild()只是对performRebuild()的调用做了一些保护,防止不必要的 build 行为,核心还是要看performRebuild()
performRebuild()
// code 2.5
// ComponenetElement
void performRebuild() {
Widget? built;
try {
// ...
built = build();
} catch(e, stack) {
// build 出错时用来顶替页面布局的占位 Widget
built = ErrorWidget.builder(...);
} finnaly {
super.performRebuild(); // code 2.6,将 _dirty 标记为 false
}
try {
_child = updateChild(_child, built, slot);
...
} catch (e, stack){
// updateChild 出错时用来顶替页面布局的占位 Widget
built = ErrorWidget.builder(...);
_child = updateChild(null, built, slot);
}
}
// code 2.6
// Element
void perfromRebuild() {
_dirty = false;
}
build() 方法是不是很熟悉?没错,对于StatelessElement来说,它就是Widget.build();而对于StatefulElement来说,它就是State.build()。这里关键处理就两个:首先是获取到childWidget,也就是built;第二个就是执行updateChild()方法进行 child 的更新操作。
updateChild()
// code 2.7
// ComponentElement
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
// newWidget 为 null 时,表示这个节点已经清空了
if (newWidget == null) {
if (child != null) {
deactivateChild(child);
}
return null;
}
final Element newChild;
if (child != null) {
// 当前已经有 child element 存在了
...
if(child.widget == newWidget) {
// 同一个 child,更新 slot 即可。
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
newChild = child;
} else if(Widget.canUpdate(child.widget, newWidget)) {
// 同一类 widget,直接通过 newWidget 走更新操作
if (child.slot != newSlot) {
updateSlotForChild(child, newSlot);
}
...
child.update(newWidget);
...
newChild = child;
} else {
// 不是同一类 widget,移除当前 element,并创建新的
deactivateChild(child);
...
newChild = inflateWidget(newWidget, newSlot);
}
} else {
// 当前没有 element,走创建
newChild = inflateWidget(newWidget, newSlot);
}
...
return newChild;
}
updateChild 方法的作用是移除、更新或者创建childElement,移除操作是deactivateChild(child),更新操作是child.update(newWidget),创建操作是inflateWidget(newWidget, newSlot)。
我们知道,Element是通过Widget“create”出来的。Widget是基础,当widget为空时,其实就表示这个节点已经不存在了,要执行移除操作;当widget不为空时,再判断是要创建还是要更新。假如当前child已经存在,通过判断child.widget跟newWidget是不是一类,如果是一类,表示可以复用,进行更新操作;如果不是一类,就表示newWidget已经是另外一种类型了,要将当前的element给移除,并生成一个新的element挂载到节点上。
对于更新,也就是child.update(newWidget)方法,ComponentElement没有重写,但它的子类都有override,我们取StatelessElement去了解即可(其余的都大同小异)
update()
// code 2.8
// StatelessElement
void update(StatelessWidget newWidget) {
super.update(newWidget); // 见 code 2.10
...
rebuild(force: true); // 见 code 2.5
}
// code 2.9,Element 中定义的 update 方法
void update(covariant Widget newWidget) {
...
_widget = widget;
}
代码很简单,就是执行的code 2.4中的rebuild操作。此时又形成了一个循环。
inflateWidget
// code 2.10
// ComponentElement
Element inflateWidget(Widget newWidget, Object? newSlot){
...
try {
final Key? key = newWidget.key;
if (key is GlobalKey) {
// 查看 GlobalKey 是否有缓存,如果有则从缓存里面取
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
...
newChild._activeWithParent(this, newSlot);
...
// 更新,见 code 2.8
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
...
return updatedChild;
}
}
// 创建 element
final Element newChild = newWidget.createElement();
...
// 挂载 element,code 2.1 中介绍的
newChild.mount(this, newSlot);
...
return newChild;
} finally {
...
}
}
先判断是否有GlobalKey缓存,如果有缓存则从缓存里面取出element执行update操作;没有则通过createElement()方法创建出newChild之后,执行mount处理。从一开始的挂载自身到这里的挂载child,代码又回到了code 2.1,形成一个循环操作。
总结
所谓挂载(mount),即是为了形成树的节点,根据上一章介绍到的AbstractNode结合code 2.2中的代码,其实核心就是将_parent指向上一个节点。根据上述代码逻辑,对文章开头提到的流程图做个完整性补充

转载自:https://juejin.cn/post/7259234035393249317