本文是『 深刻浅出 Flutter Framework 』系列文章的第三篇,主要围绕 Element 相关内容进行分析介绍,包括 Element 分类、Element 与其余几个核心元素的关系、Element 生命周期以及核心方法解读等。git
本文同时发表于个人我的博客github
本系列文章将深刻 Flutter Framework 内部逐步去分析其核心概念和流程,主要包括:json
经过『 深刻浅出 Flutter Framework 之 Widget 』的介绍,咱们知道 Widget 本质上是 UI 的配置数据 (静态、不可变),Element 则是经过 Widget 生成的『实例』,二者间的关系就像是 json 与 object。设计模式
同一份配置 (Widget) 能够生成多个实例 (Element),这些实例可能会被安插在树上不一样的位置。less
UI 的层级结构在 Element 间造成一棵真实存在的树「Element Tree」,Element 有 2 个主要职责:ide
build
方法去建立。同时,该类型 Element 都只有一个子节点 (single child);原生型 Element,只有 MultiChildRenderObjectElement 是多子节点的,其余都是单子节点。post
同时,能够看到,Element
实现了BuildContext
接口 —— 咱们在 Widget 中遇到的context
,其实就是该 Widget 对应的 Element。动画
在继续以前有必要先了解一下 Element 与其余几个核心元素间的关系,以便在全局上有个认识。 ui
上述这些关系并非全部类型的 Element 都有,如:「Render Object」只有「RenderObject Element」才有,State 只有「Stateful Element」才有。this
Element 做为『实例』,随着 UI 的变化,有较复杂的生命周期:
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 件事:
owner._inactiveElements
中,在添加过程当中会对『以该 element 为根节点的子树上全部节点』调用deactivate
方法 (移除的是整棵子树)。void deactivateChild(Element child) {
child._parent = null;
child.detachRenderObject();
owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}
复制代码
此时,element 处于 "inactive" 状态,并从屏幕上消失,该状态一直持续到当前帧动画结束;
从 element 进入 "inactive" 状态到当前帧动画结束期间,其还有被『抢救』的机会,前提是『带有「global key」&& 被从新插入树中』,此时:
owner._inactiveElements
中移除;activate
方法 (它们又复活了!);上述过程经历这几个方法:
Parent Element.inflateWidget
-->Parent Element._retakeInactiveElement
-->BuildOwner._inactiveElements.remove
-->Child Element._activateWithParent
...
对于全部在当前帧动画结束时未能成功『抢救』回来的「Inactive Elements」都将被 unmount;
至此,element 生命周期圆满结束。
下面对 Element 中的几个核心方法进行简单介绍:
updateChild
是 flutter framework 中的核心方法之一:
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
if (child != null) {
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
return child;
}
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
return child;
}
deactivateChild(child);
assert(child._parent == null);
}
return inflateWidget(newWidget, newSlot);
}
复制代码
在「Element Tree」上,父节点经过该方法来修改子节点对应的 Widget。
newWidget
== null
—— 说明子节点对应的 Widget 已被移除,直接 remove child element (若有);child
== null
—— 说明 newWidget 是新插入的,建立子节点 (inflateWidget);child
!= null
—— 此时,分为 3 种状况:
updateSlotForChild
修改 child.slot 便可;Widget.canUpdate
判断是否能够用 newWidget 修改 child element,若能够,则调用update
方法;子类通常不须要重写该方法,该方法有点相似设计模式中的『模板方法』。
在更新流程中,若新老 Widget.[runtimeType && key] 相等,则会走到该方法。 子类须要重写该方法以处理具体的更新逻辑:
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
复制代码
基类中的update
很简单,只是对_widget
赋值。
子类重写该方法时必须调用 super.
父类
ComponentElement
没有重写该方法
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
复制代码
经过rebuild
方法触发重建 child widget (第 4 行),并以此来 update child element,期间会调用到StatelessWidget.build
方法 (也就是咱们写的 Flutter 代码)。
组合型 Element 都会在
update
方法中触发rebuild
操做,以便从新 build child widget。
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
_dirty = true;
_state._widget = widget;
try {
_state.didUpdateWidget(oldWidget) as dynamic;
}
finally {
}
rebuild();
}
复制代码
相比StatelessElement
,StatefulElement.update
稍微复杂一些,须要处理State
,如:
_widget
属性;State.didUpdateWidget
(熟悉么)。最后,一样会触发rebuild
操做,期间会调用到State.build
方法。
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
super.update(newWidget);
updated(oldWidget);
_dirty = true;
rebuild();
}
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
Widget build() => widget.child;
复制代码
ProxyElement.update
方法须要关注的是对updated
的调用,其主要用于通知关联对象 Widget 有更新。 具体通知逻辑在子类中处理,如:InheritedElement
会触发全部依赖者 rebuild (对于 StatefulElement 类型的依赖者,会调用State.didChangeDependencies
)。
ProxyElement 的build
操做很简单:直接返回widget.child
。
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
复制代码
RenderObjectElement.update
方法调用了widget.updateRenderObject
来更新「Render Object」(熟悉么)。
SingleChildRenderObjectElement
、MultiChildRenderObjectElement
是RenderObjectElement
的子类。
void update(SingleChildRenderObjectWidget newWidget) {
super.update(newWidget);
_child = updateChild(_child, widget.child, null);
}
复制代码
第 3 行,经过newWidget.child
调用updateChild
方法递归修改子节点。
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
_children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
}
复制代码
上述实现看似简单,实则很是复杂,在updateChildren
方法中处理了子节点的插入、移动、更新、删除等全部状况。
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);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild;
}
}
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
复制代码
inflateWidget
属于模板方法,故通常状况下子类不用重写。
该方法的主要职责:经过 Widget 建立对应的 Element,并将其挂载 (mount) 到「Element Tree」上。
若是 Widget 带有 GlobalKey,首先在 Inactive Elements 列表中查找是否有处于 inactive 状态的节点 (即刚从树上移除),如找到就直接复活该节点。
主要调用路径来自上面介绍的updateChild
方法。
当 Element 第一次被插入「Element Tree」上时,调用该方法。因为此时 parent 已肯定,故在该方法中能够作依赖 parent 的初始化操做。通过该方法后,element 的状态从 "initial" 转到了 "active"。
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner;
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._register(this);
}
_updateInheritance();
}
复制代码
还记得BuildOwner
吗,正是在该方法中父节点的 owner 传给了子节点。 若是,对应的 Widget 带有 GlobalKey,进行相关的注册。 最后,继承来自父节点的「Inherited Widgets」。
子类重写该方法时,必须调用 super。 关于「Inherited Widgets」,后文会详细分析
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_firstBuild();
}
void _firstBuild() {
rebuild();
}
复制代码
组合型 Element 在挂载时会执行_firstBuild->rebuild
操做。
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
复制代码
在RenderObjectElement.mount
中作的最重要的事就是经过 Widget 建立了「Render Object」(第 3 行),并将其插入到「RenderObject Tree」上 (第 4 行)。
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
}
复制代码
SingleChildRenderObjectElement
在 super (RenderObjectElement
) 的基础上,调用updateChild
方法处理子节点,其实此时_child
为nil
,前面介绍过当 child 为nil
时,updateChild
会调用inflateWidget
方法建立 Element 实例。
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_children = List<Element>(widget.children.length);
Element previousChild;
for (int i = 0; i < _children.length; i += 1) {
final Element newChild = inflateWidget(widget.children[i], previousChild);
_children[i] = newChild;
previousChild = newChild;
}
}
复制代码
MultiChildRenderObjectElement
在 super (RenderObjectElement
) 的基础上,对每一个子节点直接调用inflateWidget
方法。
void markNeedsBuild() {
if (!_active)
return;
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
复制代码
markNeedsBuild
方法其实在介绍BuildOwer时已经分析过,其做用就是将当前 Element 加入_dirtyElements
中,以便在下一帧能够rebuild。 那么,哪些场景会调用markNeedsBuild
呢?
State.setState
—— 这个在介绍 Widget 时已分析过了;Element.reassemble
—— debug hot reload;Element.didChangeDependencies
—— 前面介绍过当依赖的「Inherited Widget」有变化时会致使依赖者 rebuild,就是从这里触发的;StatefulElement.activate
—— 还记得activate
吗?前文介绍过当 Element 从 "inactive" 到 "active" 时,会调用该方法。为何StatefulElement
要重写activate
?由于StatefulElement
有附带的 State,须要给它一个activate
的机会。子类通常没必要重写该方法。
void rebuild() {
if (!_active || !_dirty)
return;
performRebuild();
}
复制代码
该方法逻辑很是简单,对于活跃的、脏节点调用performRebuild
,在 3 种场景下被调用:
BuildOwner.buildScope
;Element.mount
调用;update
方法内被调用。上述第 二、3 点仅「Component Element」须要
Element 基类中该方法是no-op
。
void performRebuild() {
Widget built;
built = build();
_child = updateChild(_child, built, slot);
}
复制代码
对于组合型 Element,rebuild 过程其实就是调用build
方法生成「child widget」,再由其更新「child element」。
StatelessElement.build:
Widget build() => widget.build(this);
StatefulElement.build:Widget build() => state.build(this);
ProxyElement.build:Widget build() => widget.child;
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
复制代码
在渲染型 Element 基类中只是用 Widget 更新了对应的「Render Object」。 在相关子类中能够执行更具体的逻辑。
至此,Element 的核心方法基本已介绍完,是否是有点晕乎乎的感受?inflateWidget
、updateChild
、update
、mount
、rebuild
以及performRebuild
等你中有我、我中有你,再加上不一样类型的子类对这些方法的重写。
下面,咱们以 Element 生命周期为切入点将这些方法串起来。 对于一个 Element 节点来讲在其生命周期内可能会历经几回『重大事件』:
inflateWidget
,随之被挂载到「Element Tree」上, 此后递归建立子节点;
parent.updateChild
->child.update
;
rebuild
方法(调用场景上面已分析);
在 Element 基类中有这样两个成员:
Map<Type, InheritedElement> _inheritedWidgets;
Set<InheritedElement> _dependencies;
复制代码
它们是干吗用的呢?
_inheritedWidgets
—— 用于收集从「Element Tree」根节点到当前节点路径上全部的「Inherited Elements」; 前文提到过在mount
方法结束处会调用_updateInheritance
: 如下是 Element 基类的实现,能够看到子节点直接得到父节点的_inheritedWidgets
:void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
复制代码
如下是InheritedElement
类的实现,其在父节点的基础上将本身加入到_inheritedWidgets
中,以便其子孙节点的_inheritedWidgets
包含它 (第 8 行):
void _updateInheritance() {
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
复制代码
_dependencies
—— 用于记录当前节点依赖了哪些「Inherited Elements」,一般咱们调用context.dependOnInheritedWidgetOfExactType<T>
时就会在当前节点与目标 Inherited 节点间造成依赖关系。在 Element 上提供的便利方法
of
,通常殾会调用dependOnInheritedWidgetOfExactType
。
同时,在InheritedElement
中还有用于记录全部依赖于它的节点:final Map<Element, Object> _dependents
。 最终,在「Inherited Element」发生变化,须要通知依赖者时,会利用依赖者的_dependencies
信息作一下 (debug) check (第 4 行):
void notifyClients(InheritedWidget oldWidget) {
for (Element dependent in _dependents.keys) {
// check that it really depends on us
assert(dependent._dependencies.contains(this));
notifyDependent(oldWidget, dependent);
}
}
复制代码
至此,Element 相关的内容基本已介绍完。总结提炼一下:
最后,强烈推荐Keys! What are they good for?这篇文章,对于理解本文相关的内容有很大的帮助。