- 对Element的新建更新等流程分析
- 分析BuildOwner的作用
- State的生命周期与Element的联系
更详细的文章请看这里,详细介绍了Widget、Element...
Element
Element生命周期
被创建
开始于parent调用inflateWidget,随之被挂载到Element Tree,然后递归子节点
parentElement.inflateWidget新建childElementchildElement.mount挂载
Component Element
childElement._firstBuild->...->build,调用build构建界面childElement.updateChild继续更新子节点- ...
RenderObject Element
widget.createRenderObject,创建RenderObject对象attachRenderObject(),将renderObject对象附加render Tree上- 如果是single child,直接更新child,如果是muti child,遍历child执行infalteWidget
被更新
由Element Tree上的祖先节点传递下来的更新操作
parentElement.updateChild根据方法canUpdate(如果为true)更新子元素childElement.update开始更新子元素(基类Element只把老的widget替换成了新传入的newWidget)
Component Element
childElement.rebuild->childElement.performRebuild....(下面的细节和被"创建"的流程一样)
RenderObject Element
widget(child).updateRenderObject,当是多子节点元素时调用updateChildren遍历孩子节点执行updateChild;单子节点调用updateChild更新孩子
被重建
更新后的重建操作,重建->递归更新子节点
被销毁
element 节点所在的子树随着 UI 的变化被移除。
可以看到updateChild是更新子节点的入口,在这里决定了是走inflateWidget新建,还是走update
- 如果新建,
parentElement.updateChild->parentElement.inflateWidget->widget.createElement->childElement.mount-> .... ->childElemnet.updateChild-> ... - 如果更新,
parentElement.updateChild->childElement.update-> ... ->childElemnet.updateChild-> ...
注意,关于Stateful Element的更新和重建操作
- 更新的关键操作是
update()方法,是通过parent调用updateChild中调用- 该场景是:parent(或者某个祖先节点)在经过
setState后被加入到owner._dirtyElements中,并在下一帧时候调用了rebuild引起的子节点更新 - 调用链是:rebuild -> performBuild
- 该场景是:parent(或者某个祖先节点)在经过
- 重建的关键操作是
rebuild()方法,是当前节点被加入到_dirtyElements中- 该场景是当前节点setState被标记
简而言之,更新是指element当作子节点,重建是指element当作根节点
重要方法
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot){}
更新child,在此方法里面决定对child的操作(新建、更新、移除)
参数
- child:child element
- newWidget:对应新的widget
- newSlot:槽口
Element
Element? updateChild(Element? child, Widget? newWidget, Object? 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;
}
| newWidget == null | newWidget != null | |
|---|---|---|
| child = null | return null | 新建child,return newChild |
| child != null | child移除,return null | 如果可以更新位置就更新位置;如果需要更新widget就更新widget;否则先移除child再新建child |
Element inflateWidget(Widget newWidget, Object? newSlot) {}
通过 Widget 创建对应的 Element,并将其挂载 (mount) 到「Element Tree」上。
Element inflateWidget(Widget newWidget, Object? 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;
}
- 通过
newWidget.createElement()创建element对象newChild - 调用
newChild.mount(this, newSlot)进行挂载 - 如果 Widget 带有 GlobalKey,首先在 Inactive Elements 列表中查找是否有处于 inactive 状态的节点 (即刚从树上移除),如找到就直接复活该节点。
- 此方法一般都是通过
updateChild()调用
void mount(Element? parent, Object? newSlot){}
将此元素添加到给定父节点的给定槽位的Element Tree中。
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;//传递BuilderOwner对象
}
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);//如果是GlobalKey,将该element注册到owner管理的_globalKeyRegistry中
}
_updateInheritance(); //更新Inheritance
}
- 获取parent的owner(我们知道BuilderOwner是从根节点往传递的,子节点获取owner的方式就是在mount方法)
- 将GlobalKey类型的widget注册到owner管理的_globalKeyRegistry中
- 更新Inheritance(获取parent的_inheritedWidgets,inheritedWidegts也是从parent往下传递的)
ComponentElement (extends Element)
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_firstBuild();
}
- 通过上面的流程图我们知道,在Element被创建时候会调用
mount -> _firstBuild - rebuild -> performRebuild -> build -> updateChild
RenderObjectElement (extends Element)
@override
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
widget.createRenderObject(this)创建RenderObject对象attachRenderObject(newSlot),将RenderObject添加到Render Tree中的newSolt位置
SingleChildRenderObjectElement(extends RenderObjectElement)
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
}
- 和流程图一致,single child:
createRenderObject->attachRenderObject->updateChild
MultiChildRenderObjectElement(extends RenderObjectElement)
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance);
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;
}
- 和流程图一致,multi child:
createRenderObject->attachRenderObject->遍历children调用inflateWidget
void update(covariant Widget newWidget)
Element
void update(covariant Widget newWidget) {
_widget = newWidget;
}
- Element实现的update只有一个作用,更改用于配置此element的widget
ComponentElement
未实现
StatelessElement (extends ComponentElement)
void update(StatelessWidget newWidget) {
super.update(newWidget);
_dirty = true;
rebuild();
}
- 将其标记为_dirty
- 从流程图中的“被更新”步骤我们看出,
update()会执行rebuild(),完整流程是:StatelessElement.update -> Element.rebuild -> Component.performRebuild -> StatelessWidget.build -> updateChild
StatefulElement(extends ComponentElement)
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = state._widget!;
_dirty = true;
state._widget = widget as StatefulWidget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
rebuild();
}
- 和StatelessElement差不多,不同的是
- state._widget = widget(众所周知,StatefulElement的widget是被state持有的)
- 调用
didUpdateWidget方法(这下应该清楚State生命周期里的didUpdateWidget是什么时候被调用了,当element被parent更新时)
ProxyElement (extends ComponentElement)
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
updated(oldWidget);
_dirty = true;
rebuild();
}
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
- rebuild逻辑与StatelessElememt一样,唯一不同的是build方法直接返回
widget.child - 更需要关注
updated方法,其主要用于通知关联对象 Widget 有更新。 具体通知逻辑在子类中处理- InheritedElement,会触发所有依赖者 rebuild (对于 StatefulElement 类型的依赖者,会调用
State.didChangeDependencies - ParentDataWidget,会触发
parentDataWidget.applyParentData(renderObject)更新renderObject的信息时
- InheritedElement,会触发所有依赖者 rebuild (对于 StatefulElement 类型的依赖者,会调用
InheritedElement
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (final Element dependent in _dependents.keys) {
notifyDependent(oldWidget, dependent);
}
}
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
- 很明显了,遍历依赖对它的对象,并调用
didChangeDependencies()(现在我们知道Widget生命周期的didChangeDependencies什么时候会被调用了)
ParentDataElement (extends ComponentElement)
void notifyClients(ParentDataWidget<T> oldWidget) {
_applyParentData(widget);
}
void _applyParentData(ParentDataWidget<T> widget) {
void applyParentDataToChild(Element child) {
if (child is RenderObjectElement) {
child._updateParentData(widget);
} else {
assert(child is! ParentDataElement<ParentData>);
child.visitChildren(applyParentDataToChild);
}
}
visitChildren(applyParentDataToChild);
}
void _updateParentData(ParentDataWidget<ParentData> parentDataWidget) {
bool applyParentData = true;
if (applyParentData)
parentDataWidget.applyParentData(renderObject);
}
- 如果child是RenderObjectElement执行
_updateParentData方法,_updateParentData调用到widget里面,并传入renderObject
- 如果child不是RenderObjectElement,递归子元素
RenderObjectElement(extends Element)
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
_performRebuild(); // calls widget.updateRenderObject()
}
@override
void performRebuild() {
_performRebuild(); // calls widget.updateRenderObject()
}
void _performRebuild() {;
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
- 很好理解,更新RenderObject
- 注意
updateRenderObject是被widget调用的
SingleChildRenderObjectElement (extends RenderObjectElement)
@override
void update(SingleChildRenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_child = updateChild(_child, widget.child, null);
}
- 和流程图一致
MultiChildRenderObjectElement (extends RenderObjectElement)
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
_children = updateChildren(_children, widget.children, forgottenChildren: _forgottenChildren);
_forgottenChildren.clear();
}
- 上述实现看似简单,实则非常复杂,在
updateChildren方法中处理了子节点的插入、移动、更新、删除等所有情况。
markNeedsBuild
将元素标记为dirty,并将其添加到widget的全局列表(owner)中 在下一帧重建。
子类无需重写。
调用该方法的情况:
- setState
- reassemble (热重载)
- Element.didChangeDependencies (依赖变更)
- StatefulElement.activate (重新被激活)
rebuild
对于active&&dirty的节点调用performRebuild(),
调用该方法的情况:
- Element.mount(挂载时)
- BuildOwner.buildScope (新的一帧时,owner在该方法中对dirtyElements数组遍历调用rebuild)
- Element.update
performRebuild
ELement基类未实现
ComponentElement
void performRebuild() {
Widget built;
built = build();
_child = updateChild(_child, built, slot);
}
- 对于组合型 Element,rebuild 过程其实就是调用
build方法生成「child widget」,再由其更新「child element」。
RenderObjectElement
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
- 在渲染型 Element 基类中只是用 Widget 更新了对应的「Render Object」。 在相关子类中可以执行更具体的逻辑
State
State的生命周期是比较重要的知识点,借图
关于Widget、State和Element的关系:
- Widget只是Element的UI配置,Element会根据Widget的变化而更新(节点插入、更新、删除、移动等)
- Element通过parent,child组成了Element Tree
- Element持有Widget,Render Object
- State是绑定在Element上的,而不是Stateful Widget上的
为什么要用StatefulWidget,和Stateless的区别,以及State的作用
众所周知,widgets are immutable,所以如果我们想修改StatelessWidget是不允许的; 这时候我们可以使用StatefulWidget,
- StatefulWidget本身也是不可变的,通过createState创建State对象并将其绑定在对应的Element上,
- State对象管理了StatefulWidget的逻辑和内部状态,并维护了一系列的生命周期(上图),它的生命周期都是和Elemetn关联的
- 当需要改变UI时,在State里面调用
setState方法将当前Element标记,并在下一帧重回 - 当有状态组件的配置发生更改时,StatefulWidget 将会被丢弃并重建,而 State 不会重建,框架会更新 State 对象中 widget 的引用,这极大的减轻了系统重建有状态组件的工作。此方式对动画来说极为重要,由于 State 不会被重建,保留了前面的状态,不断的根据前一个状态计算下一个状态并重建其widget,达到动画的效果。
通过上面的总结,我们应该明白,为了减少不必要的重绘,尽量将需要改变状态的小部件封装成单个的StatefulWidget,而不是无所顾忌的在比较“高层”的地方调用setState
为什么能够在State中访问widge
StatefulElement(StatefulWidget widget)
: state = widget.createState(),
super(widget) {
state._element = this;
state._widget = widget;
}
- 在StatefulElement初始化时候,将当前widget绑定给state
State里面访问的context是什么
abstract class Element extends DiagnosticableTree implements BuildContext
- Element实现了BuildContext,所以BuildContext就是Element
State生命周期与Element
State的生命周期都是被Element管理的
State():在StatefulElement被创建时创建StateStatefulElement(StatefulWidget widget)->state = widget.createState()
initState():在StatefulElement被mount(挂载)到树上后,调用initStatemount()->_firstBuild()->state.initState()
didChangeDependencies():依赖变更,触发场景:- 在StatefulElement被挂载完成时(_firstBuild)
- 在StatefulElement被触发更新时
update->rebuild->performRebuild->didChangeDependencies
- 依赖了InheritedWidget的widget,在InheritedWidget被更新时会被调用
build():StatefulWidget构建子widget的方法,触发场景有:- StatefulElement被挂载时(mount)
mount->_firstBuild->rebuild->performRebuild->build
- StatefulElement被重建时
rebuild->performRebuild->build
- StatefulElement被更新时
update->rebuild->performRebuild->build
- StatefulElement被挂载时(mount)
didUpdateWidget():State要更新了,有个注意点,这个方法只有在element作为子节点被更新时才会触发;当element被当作根节点重建时不会触发(更新和重建的区别看上面)- StatefulElement被更新时
update->state.didUpdateWidget
- StatefulElement被更新时
deactivate():Element被设置为不活跃时调用(即将被移除)- 不活跃是指StatefulElement的状态为inactive,此时是指该element从树上取下,并即将销毁
- 当Element需要被移除时,加入到BuildOwner管理的
_inactiveElements中,在加入的过程中会执行deactivate()updateChild->deactivateChild->owner!._inactiveElements.add(child)->_deactivateRecursively(element)->element.deactivate()->state.deactivate()
build():注意流程图里面还可能会调用一次build,是因为inactive状态的element可能还会被复活(前提是没被丢弃)dispose:走到这里就说明StatefulElement彻底被丢弃了,调用时机是:- 被加入到_inactiveElements的element在当前帧结束时执行
element.unmount()-
_inactiveElements._unmountAll()->element.unmount()->state.dispose();
-
- 被加入到_inactiveElements的element在当前帧结束时执行
InheritedWidget
- 传递:InheritedElement是从根节点往下传递的(是放入Map中传递)
- 获取:通过BuildContext(就是Element对象),查找自身存储InheritedElement的Map,返回相应的InheritedWidget,并且会把当前element加入到InheritedElement管理的_dependents中
- 更新:InheritedElement会遍历_dependents调用
didChangeDependencies(),随即出发state的didChangeDependencies()
BuildOwner
BuildOwner是管理Widget的对象
主要有三个作用:
- 在 UI 更新过程中跟踪、管理需要 rebuild 的 Element (「dirty elements」);
- 在有「dirty elements」时,及时通知引擎,以便在下一帧安排上对「dirty elements」的 rebuild,从而去刷新 UI;
- 管理处于 "inactive" 状态的 Element。
创建及传递时机
- 在WidgetsBinding的初始化方法里创建一个_buildOwner实例。
- 传递给RootElement
- 通过
WidgetsBinding.attachRootWidget创建一个RenderObjectToWidgetAdapter对象 RenderObjectToWidgetAdapter.attachToRenderTree将owner分配给RenderObjectToWidgetElement
- 通过
- 传递给child
-
在每次将Element对象挂载(mount)到树上时,会将
parent.owner传递给child
-
管理需要rebuild的Element
添加标记并加入_dirtyElements数组
在State里面调用setState(){}时,会将该Element对象_element标记为dirty(脏的),并将该_element加入到BuildOwner对象管理的数组中(_dirtyElements)
void setState(VoidCallback fn) {
_element!.markNeedsBuild();
}
void markNeedsBuild() {
_dirty = true;
owner!.scheduleBuildFor(this);
}
void scheduleBuildFor(Element element) {
_dirtyElements.add(element);
element._inDirtyList = true;
}
rebuild
随后在下一帧时候对_dirtyElements中的Element对象调用rebuild()方法(WidgetsBinding.drawFrame -> WidgetsBinding.buildScop)
WidgetsBinding.drawFrame:生成每帧的方法WidgetsBinding.buildScope:更新widget Tree的方法
void drawFrame()
buildOwner!.buildScope(renderViewElement!);
}
void buildScope(Element context, [ VoidCallback? callback ]) {
_dirtyElements[index].rebuild();
}
(以上代码段并非这么简单,比如buildScope有其他逻辑(排序、将Element对象解除标记等等)。);
管理inactive的Element
BuildOwner管理了非活跃的element,并将其存放于_inactiveElements
添加到_inactiveElements
当一个element需要移除时,BuildOwner将该element加入_inactiveElements
class _InactiveElements {
//添加element到_InactiveElements管理的_elements中
void add(Element element) {
if (element._lifecycleState == _ElementLifecycle.active)
_deactivateRecursively(element);
_elements.add(element);
}
//递归方法:以该element为根节点的子树的所有节点调用`deactivate`(移除的是整颗子树)
static void _deactivateRecursively(Element element) {
element.deactivate();
element.visitChildren(_deactivateRecursively);
}
}
从_inactiveElements移除
当deactivate的element又需要使用时(前提是没有被丢弃),将其从_inactiveElements中移除
class _InactiveElements {
void remove(Element element) {
_elements.remove(element);
}
}
丢弃element
当element需要彻底丢弃时(当前帧结束,并且element在_inactiveElements中),调用element及其child的unmount
_inactiveElements._unmountAll();
class _InactiveElements {
void _unmount(Element element) {
element.visitChildren((Element child) {
_unmount(child);
});
element.unmount();
}
void _unmountAll() {
final List<Element> elements = _elements.toList()..sort(Element._sort);
_elements.clear();
try {
elements.reversed.forEach(_unmount);
} finally {
assert(_elements.isEmpty);
_locked = false;
}
}
}