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