Flutter核心类分析深入理解Element
Posted 牧羊人.阿标
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter核心类分析深入理解Element相关的知识,希望对你有一定的参考价值。
文章目录
背景
通过前面的文章深入理解Widget我们知道,Widget本质上是Element的数据配置项,Element是通过Widget.createElement
生成实例。同一个Widget可以创建多个Element。
Element分类
我们首先来从Element的继承关系着手
根据继承关系图Element可以分为两类:
-
Component Element
Component Element
,对于的Widget是Component Widget和Proxy Widget,特点是子节点对于的Widget都需要通过build
方法去创建。该类型的Element都只有一个子节点(single child); -
RenderObjectElement
Renderer Widget
,RenderObjectWidget
是它的配置信息,RenderObjectElement根据子类的不同包含的子阶段数量也不同:LeafRenderObjectElement 没有子节点,RootRenderObjectElement、SingleChildRenderObjectElement 有一个子节点,MultiChildRenderObjectElement 有多个子节点。
核心源码分析
由于Element的源码比较冗长,这里例举几个核心方法进行分析:
Element.updateChild
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot)
if (newWidget == null)
if (child != null)
deactivateChild(child);
return null;
final Element newChild;
if (child != null)
bool hasSameSuperclass = true;
if (hasSameSuperclass && child.widget == newWidget)
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget))
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
else
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
else
newChild = inflateWidget(newWidget, newSlot);
return newChild;
该方法子类一般不需要复写,Element.updateChild
方法第一次调用是由RenderObjectToWidgetAdapter.attachToRenderTree
→RenderObjectToWidgetElement.mount
→RenderObjectToWidgetElement._rebuild
→Element.updateChild
。
整个流程的方法后面都会讲到
该方法的主要作用就是根据对于的Widget的子Widget,来创建或者更新对于子Widget的ELement。根据上面的if条件 整理逻辑为:
-
newWIdget == null
说明子节点对应的Widget已经被Remove,直接Remeove child
-
child == null
说明 newWidget 是新插入的,创建子节点 (inflateWidget);
-
child != null 再分为三种情况
-
child.widget == newWidget
说明 child.widget 前后没有变化,若 child.slot != newSlot 表明子节点在兄弟结点间移动了位置,通过
updateSlotForChild
修改 child.slot 即可; -
Widget.canUpdate
canUpdate
判断是否可以用 newWidget 修改 child element,若可以,则调用update
方法; -
其他情况
Remove child然后再调用ifnflateWidget创建新的child
-
Element.inflateWidget
Element inflateWidget(Widget newWidget, dynamic newSlot)
final Key? key = newWidget.key;
if (key is GlobalKey)
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null)
newChild._activateWithParent(this, newSlot);
// 将inactive状态下的Element再次插入到Element树中
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild!;
//创建Element
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
该方法的主要职责:通过 Widget 创建对应的 Element,并将其挂载 (mount) 到Element Tree
上。调用路径来自上面的updateChild
方法。
Element.upate
从上面的Element.updateChild
方法中,我们知道当Widget.canUpdate
返回为true的时候,会调用到该方法。子类子类需要重写该方法以处理具体的更新逻辑,同时该方法注解为:@mustCallSuper,也就是说子类重写该方法还需要手动调用super方法:
@mustCallSuper
void update(covariant Widget newWidget)
_widget = newWidget;
可以看到基类中update很简单,只是对_widget做了赋值。
StatelessElement.update
@override
void update(StatelessWidget newWidget)
super.update(newWidget);
_dirty = true;
rebuild();
该方法的作用首先就是标记该Element的_dirty为true,再调用rebuild()
方法重建child element。该方法会调用到Element.build
方法,也就是会调用到Element的子类StatelessWidget.build方法。(不同子类的update方法会调用到自己的build方法)
StatefulElement.update
@override
void update(StatefulWidget newWidget)
super.update(newWidget);
final StatefulWidget oldWidget = state._widget!;
final dynamic debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
_dirty = true;
state._widget = widget as StatefulWidget;
rebuild();
相比上面的StatelessElement.update方法,StatefulElement.update多完成了一些任务,它需要处理一些State
的逻辑:
- 修改state._widget的属性
- 调用state.didUpdateWidget方法(不熟悉可以看上文:深入理解Widget)
- 最后再
rebuild
阶段会调用到自己StatefulElement.build方法,间接调用到了State.build方法
ProxyElement.update
@override
void update(ProxyWidget newWidget)
final ProxyWidget oldWidget = widget;
updated(oldWidget);
_dirty = true;
rebuild();
@protected
void updated(covariant ProxyWidget oldWidget)
notifyClients(oldWidget);
@protected
void notifyClients(covariant ProxyWidget oldWidget);
ProxyElement.update
方法需要关注的是对update
的调用链,通过源码可以看出它的作用主要是用于通知关联对象Widget有更新。具体通知可在子类中处理,详细分析见深入理解数据共享InheritedWidget.
RenderObjectElement.update
@override
void update(covariant RenderObjectWidget newWidget)
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
RenderObjectElement.update
方法调用了widget.updateRenderObject
来更新Render Object。
SingleChildRenderObjectElement.update
@override
void update(SingleChildRenderObjectWidget newWidget)
super.update(newWidget);
_child = updateChild(_child, widget.child, null);
SingleChildRenderObjectElement
继承自RenderObjectElement
直接调用updateChild递归修改子节点
MultiChildRenderObjectElement.update
@override
void update(MultiChildRenderObjectWidget newWidget)
super.update(newWidget);
_children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
_forgottenChildren.clear();
在updateChildren
方法中处理了子节点的插入、移动、更新、删除等所有情况。
Element.mount
@mustCallSuper
void mount(Element? parent, dynamic newSlot)
_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
_owner = parent.owner;
final Key? key = widget.key;
if (key is GlobalKey)
key._register(this);
_updateInheritance();
BuildOwner
对象会在这里传递给child element。最后继承来自父节点的InheritedWidget
(将_inheritedWidgets
指向_parent的_inheritedWidgets
对象),该方法被注释为@mustCallSuper
,子类重写该方法时必须调用super。
ComponentElement.mount
@override
void mount(Element? parent, dynamic newSlot)
super.mount(parent, newSlot);
_firstBuild();
void _firstBuild()
rebuild();
Comonent Element 在挂载时会执行_firstBuild->rebuild
操作。
RenderObjectElement.mount
@override
void mount(Element? parent, dynamic newSlot)
super.mount(parent, newSlot);
//1
_renderObject = widget.createRenderObject(this);
//2
attachRenderObject(newSlot);
_dirty = false;
从源码可以看到RenderObjectElement.mount
方法的核心作用就两个:
widget.createRenderObject(this)
创建RenderObject
;- 通过
attachRenderObject(newSlot);
将RenderObject插入到RenderObject Tree中。
SingleChildRenderObjectElement.mount
@override
void mount(Element? parent, dynamic newSlot)
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
该方法直接调用updateChild
返回一个新的element实例;
MultiChildRenderObjectElement.mount
@override
void mount(Element? parent, dynamic newSlot)
super.mount(parent, newSlot);
final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false);
Element? previousChild;
for (int i = 0; i < children.length; i += 1)
final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild));
children[i] = newChild;
previousChild = newChild;
_children = children;
该方法循环为每个子节点调用inflateWidget
返回一个新的element实例;
Element.markNeedsBuild
void markNeedsBuild()
if (_lifecycleState != _ElementLifecycle.active)
return;
if (dirty)
return;
_dirty = true;
owner!.scheduleBuildFor(this);
markNeedsBuild
的主要作用就是将当前 Element 加入_dirtyElements
中,以便在下一帧可以rebuild。
调用markNeedsBuild场景:
-
State.setState
详细分析见文章深入理解Widget
-
Element.reassemble
只在开发过程中调用,调用于热重载齐肩
-
Element.didChangeDependencies
前面介绍过当依赖的「Inherited Widget」有变化时会导致依赖者 rebuild,就是从这里触发的
-
StatefulElement.activate
当 Element 从 “inactive” 到 “active” 时(
inflateWidget
),会调用该方法。为什么StatefulElement
要重写activate
?因为StatefulElement
有附带的 State,需要给它一个activate
的机会。
Element.rebuild
void rebuild()
if (_lifecycleState != _ElementLifecycle.active || !_dirty)
return;
performRebuild();
该方法逻辑简单,对于活跃的脏节点调用performRebuild
,在下面三种情况下被调用:
- 对于dirty element,在新一帧的绘制过程中有BUildOwner.buildScope;
- 在element挂在时候,由Element.mount调用;
- 在update方法内被调用。
Element.performRebuild
/// Called by rebuild() after the appropriate checks have been made.
@protected
void performRebuild();
基类Element中的performRebuild
是一个空实现。
CommponentElement.performRebuild
@override
void performRebuild()
Widget? built;
try
built = build();
debugWidgetBuilderValue(widget, built);
catch (e, stack)
finally
_dirty = false;
try
_child = updateChild(_child, built, slot);
catch (e, stack)
对于组合型 Element,rebuild 过程其实就是调用build
方法生成child widget
,再调用updateChild
更新child element
。
-
StatelessElement.build
build => widget.build(this)
-
StatefulElement.build
Build => state.build(this)
-
ProxyElement.build
widget.child
RenderObjectElement.performRebuild
@override
void performRebuild()
widget.updateRenderObject(this, renderObject);
_dirty = false;
在渲染型 Element 基类中只是用 Widget 更新了对应的Render Object
。在相关子类中可以执行更具体的逻辑。
Element生命周期
Element生命周期原有比这复杂得多,这里只是列出了大概的流程。
-
parent 通过
Element.inflateWidget
->Widget.createElement
创建 child element,触发场景有:UI 的初次创建、UI 刷新时新老 Widget 不匹配(old element 被移除,new element 被插入); -
parent 通过
Element.mount
将新创建的 child 插入「Element Tree」中指定的插槽处 (slot);dynamic Element.slot
——其含意对子节点透明,父节点用于确定其下子节点的排列顺序 (兄弟节点间的排序)。因此,对于单子节点的节点 (single child),child.slot 通常为 null。
另外,slot 的类型是动态的,不同类型的 Element 可能会使用不同类型的 slot,如:Sliver 系列使用的是 int 型的 index,MultiChildRenderObjectElement 用兄弟节点作为后一个节点的 slot。
对于「component element」,mount
方法还要负责所有子节点的 build (这是一个递归的过程),对于「render element」,mount
方法需要负责将「render object」添加到「render tree」上。其过程在介绍到相应类型的 Element 时会详情分析。 -
此时,(child) element 处于 active 状态,其内容随时可能显示在屏幕上;
-
此后,由于状态更新、UI 结构变化等,element 所在位置对应的 Widget 可能发生了变化,此时 parent 会调用
Element.update
去更新子节点,update 操作会在以当前节点为根节点的子树上递归进行,直到叶子节点;(执行该步骤的前提是新老 Widget.[key && runtimeType] 相等,否则创建新 element,而不是更新现有 element); -
状态更新时,element 也可能会被移除 (如:新老 Widget.[key || runtimeType] 不相等),此时,parent 将调用
deactivateChild
方法,该方法主要做了 3 件事:- 从「Element Tree」中移除该 element (将 parent 置为 null);
- 将相应的「render object」从「render tree」上移除;
- 将 element 添加到
owner._inactiveElements
中,在添加过程中会对『以该 element 为根节点的子树上所有节点』调用deactivate
方法 (移除的是整棵子树)。
-
此时,element 处于 “inactive” 状态,并从屏幕上消失,该状态一直持续到当前帧动画结束;
-
从 element 进入 “inactive” 状态到当前帧动画结束期间,其还有被『抢救』的机会,前提是『带有「global key」&& 被重新插入树中』,此时:
- 该 element 将会从
owner._inactiveElements
中移除; - 对该 element subtree 上所有节点调用
activate
方法 (它们又复活了!); - 将相应的「render object」重新插入「render tree」中;
- 该 element subtree 又进入 “active” 状态,并将再次出现在屏幕上。
- 该 element 将会从
-
对于所有在当前帧动画结束时未能成功『抢救』回来的「Inactive Elements」都将被 unmount;
总结
Element介绍到这里就结束了。这里做个总结:
- Element 与 Widget 一一对应
- 只有
Render Element
才有对应的Render Object
; - Element 作为 Widget 与 RenderObejct 间协调者,会根据 UI(Widget Tree) 的变化对Element Tree作出相应的调整,同时对RenderObject Tree进行必要的修改;
- Widget 是不可变的、无状态的,而 Element 是有状态的。
以上是关于Flutter核心类分析深入理解Element的主要内容,如果未能解决你的问题,请参考以下文章