状态对象具有以下生命周期:
- 框架通过调用 StatefulWidget.createState创建一个State对象。
- 新创建的State对象与BuildContext相关联。这种关联是永久的:State对象永远不会改变它的 BuildContext。但是,BuildContext本身可以与它的子树一起在树周围移动。此时,State对象被视为已安装。
- 框架调用initState。State的子类应该重写 initState以执行依赖于BuildContext或小部件的一次性初始化 ,当调用initState方法时,它们分别可用作上下文和 小部件属性。
- 框架调用didChangeDependencies。State的子类应该覆盖didChangeDependencies以执行涉及 InheritedWidget的初始化。如果调用了BuildContext.dependOnInheritedWidgetOfExactType ,如果继承的小部件随后发生变化或小部件在树中移动,则将再次调用didChangeDependencies方法。
- 此时,State对象已完全初始化,框架可能会多次调用其build方法来获取此子树的用户界面描述。状态对象可以通过调用它们的setState方法自发地请求重建它们的子树 ,这表明它们的某些内部状态已经以可能影响该子树中的用户界面的方式发生了变化。
- 在此期间,父小部件可能会重建并请求更新树中的此位置以显示具有相同 runtimeType和Widget.key的新小部件。发生这种情况时,框架将更新小部件属性以引用新小部件,然后 以前一个小部件作为参数调用didUpdateWidget方法。状态 对象应该覆盖didUpdateWidget以响应其关联小部件中的更改(例如,启动隐式动画)。框架总是在调用didUpdateWidget之后调用build,这意味着在didUpdateWidget中对setState的任何调用是多余的。
- 在开发过程中,如果发生热重载(无论是从命令行
flutter
工具按启动r
,还是从 IDE 启动), 都会调用reassemble方法。这提供了重新初始化在initState方法中准备的任何数据的机会。 - 如果包含State对象的子树从树中移除(例如,因为父级构建了具有不同runtimeType 或Widget.key的小部件),则框架调用deactivate方法。子类应该重写此方法以清除此对象与树中其他元素之间的任何链接(例如,如果您为祖先提供了指向后代RenderObject的指针)。
- 此时,框架可能会将此子树重新插入树的另一部分。如果发生这种情况,框架将确保调用build以使State对象有机会适应其在树中的新位置。如果框架确实重新插入此子树,它将在从树中删除子树的动画帧结束之前执行此操作。因此,State对象可以推迟释放大部分资源,直到框架调用它们的dispose 方法。
- 如果框架在当前动画帧结束时没有重新插入这个子树,框架将调用dispose,这表明这个State对象将永远不会再次构建。子类应覆盖此方法以释放此对象保留的任何资源(例如,停止任何活动动画)。
- 在框架调用dispose后,State对象被认为是未挂载的,mounted属性为 false。此时调用setState是错误的 。生命周期的这个阶段是终端:无法重新挂载已释放的State对象。
分段理解各个生命周期
生命周期1- createState
分析源码
class StatefulElement extends ComponentElement { StatefulElement(StatefulWidget widget) //可看到在创建StatefulElement 元素的时候创建了 state : _state = widget.createState(), super(widget) //这就是生命周期2的关联 state._element = this; //同时把widget 绑定到了state state._widget = widget; }
生命周期2- 开始State对象与BuildContext相关联
在上一步的构造方法中可以看到已经 state 和element 关联起来了
那是怎么关联BuildContext 的
看state 源码 找到 BuildContext 对象
BuildContext get context { //这时候state 和buildcontext关联起来了 //可以看到 BuildContext 本身就是StatefulElement return _element!; }
生命周期3- 调用initState
在 ComponentElement源码中 可以看到调用了mount
mount 就是渲染树插入
mount 的细节描述请看flutter Widget、Element和RenderObject 树的插入源码分析_阿旭哟嘿的博客-CSDN博客
@override void mount(Element? parent, Object? newSlot) { super.mount(parent, newSlot); _firstBuild(); }
//在插入渲染树的时候调用了 initstate 方法
@override void _firstBuild() { try { //可以看到这时候调用了state.initState() //注意这时候还只是树的插入步骤,所以平时获取size在这里报错, 因为先插入树,完成后,在开始测量layout 详情看flutter widget layout测量源码解析_阿旭哟嘿的博客-CSDN博客 final Object? debugCheckForReturnedFuture = state.initState() as dynamic; ...其他省略 } //这时候调用了生命周期4。 state.didChangeDependencies(); }
生命周期4 -框架调用didChangeDependencies
在上一个方法里面可以看到, 在初始化的后也会调用didChangeDependencies
其他什么时候调用?
先回顾didChangeDependencies 文档描述
当此State对象的依赖项发生更改时调用。
看源码
@pragma('vm:prefer-inline') void rebuild() { //如果当前元素的生命周期不等于活跃或者没有脏元素就不构建 //这里的生命周期只是代码内置的运行状态标记 //脏元素表示更新的widget //在setstate 时候。会把 Element标记为脏...最后引擎会调用drawFrame的buildOwner!.buildScope(renderViewElement!)在调用element.rebuild(); //细节请看flutter 绘制源码解析_阿旭哟嘿的博客-CSDN博客_flutter源码分析 if (_lifecycleState != _ElementLifecycle.active || !_dirty) return; Element? debugPreviousBuildTarget; performRebuild(); }
在看 StatefulElement 源码
@override void performRebuild() { //重建后会判断是否有改变有改变才会调用 if (_didChangeDependencies) { state.didChangeDependencies(); _didChangeDependencies = false; } super.performRebuild(); }
再来分析_didChangeDependencies。
bool _didChangeDependencies = false; @override void didChangeDependencies() { super.didChangeDependencies(); //发现变为true了 _didChangeDependencies = true; }
根据文档提示执行涉及 InheritedWidget的初始化
查看 InheritedWidget 源码
@protected void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) { //发现调用者 dependent.didChangeDependencies(); }
InheritedWidget 小部件
看描述 小部件的基类,可有效地沿树向下传播信息。
class FrogColor extends InheritedWidget { const FrogColor({ Key? key, required this.color, required Widget child, }) : super(key: key, child: child); final Color color; static FrogColor of(BuildContext context) { final FrogColor? result = context.dependOnInheritedWidgetOfExactType<FrogColor>(); return result!; } @override bool updateShouldNotify(FrogColor old) => color != old.color; }
根据上面的信息得出, 此小部件的子类, 在此部件updateShouldNotify 为ture 的情况下子部件的didChangeDependencies 会被调用
生命周期5-此时,State对象已完全初始化,框架可能会多次调用其build方法来获取此子树的用户界面描述
ComponentElement
@pragma('vm:notify-debugger-on-exception') void performRebuild() { //开始构建 built = build(); ..其他省略 }
生命周期6-在此期间,父小部件可能会重建并请求更新树中的此位置以显示具有相同 runtimeType和Widget.key的新小部件
源码分析
@override //页面更新引擎会触发drawFrame 来重新绘制 void drawFrame() { ..其他省略只显示关键代码 //表示根渲染元素 if (renderViewElement != null) //建立更新小部件树的范围,并调用给定的“callback”(如果有) buildOwner!.buildScope(renderViewElement!); }
void buildScope(Element context, [ VoidCallback? callback ]) { //其他省略只显示关键代码 //前面会找出脏元素还是重新build try { //脏元素重新build element.rebuild(); } }
Element
@pragma('vm:prefer-inline') void rebuild() { //隐藏其他代码 //开始执行 performRebuild(); }
ComponentElement
void performRebuild() { Widget? built; try { //开始获取build 获取新的widget built = build(); } catch (e, stack) { } finally { //已经获取到了, 就把脏元素置为否 _dirty = false; } try { //更新新的widget _child = updateChild(_child, built, slot); } catch (e, stack) { } }
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { final Element newChild; if (child != null) { bool hasSameSuperclass = true; //新的和旧的不一致,所以不会走 if (hasSameSuperclass && child.widget == newWidget) { //其他代码部分省略,只显示关键代码 //细节描述//flutter Widget、Element和RenderObject 树的插入源码分析_阿旭哟嘿的博客-CSDN博客 //hasSameSuperclass 判断组件和元素的类型是否一致 //如果运行类型和key一致就更新,反之不会更新 } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) { //开始调用 child.update(newWidget); //替换新的子元素 newChild = child; } } return newChild;
StatefulElement
@override void update(StatefulWidget newWidget) { super.update(newWidget); final StatefulWidget oldWidget = state._widget!; _dirty = true; state._widget = widget as StatefulWidget; try { //开始调用didUpdateWidget final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic; } finally { } //开始调用build rebuild(); }接着看调用部分源码
生命周期7- 在开发过程中,如果发生热重载, 都会调用reassemble方法
//简单理解就是调试时候用的 不在深入源码 @override void reassemble() { }
生命周期8 -如果包含State对象的子树从树中移除,则框架调用deactivate方法。
在更新部件中触发
@protected @pragma('vm:prefer-inline') Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) { ....省略其他代码只显示关键代 //如果更新后部件一样 if (hasSameSuperclass && child.widget == newWidget) { //判断树的位置,如果有改变变成树的位置,重新复制 if (child.slot != newSlot) updateSlotForChild(child, newSlot); newChild = child; //如果更新后部件类型一样key一样就调用元素自己更新 } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) { //开始当前元素 //最后每个元素都会依次调用当前方法,去判断新的部件和旧的部件是否一致, 不一致就移除 child.update(newWidget); newChild = child; } else { //如果都不满足直接移除元素(比如newWidget 为null) //里面会移除渲染树, 并添加当前元素到非活动元素集合 deactivateChild(child); //这时候会触发 state.activate() 已经和父元素绑定 newChild = inflateWidget(newWidget, newSlot); } return newChild; }
如果是多子部件的话,流程会有区别
MultiChildRenderObjectElement
@override void update(MultiChildRenderObjectWidget newWidget) { super.update(newWidget); final MultiChildRenderObjectWidget multiChildRenderObjectWidget = widget as MultiChildRenderObjectWidget; //开始更新子元素 _children = updateChildren(_children, multiChildRenderObjectWidget.children, forgottenChildren: _forgottenChildren); _forgottenChildren.clear(); }
@protected List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element>? forgottenChildren, List<Object?>? slots }) { 其他省略.... int newChildrenTop = 0; int oldChildrenTop = 0; int newChildrenBottom = newWidgets.length - 1; int oldChildrenBottom = oldChildren.length - 1 Element? previousChild; //从顶部遍历列表,同步节点,直到不再有匹配节点. //比如 老的1 2 3 4 5 新的 1 2 7 8 5 这时候循环到7的时候就会停止 3 和7 不一致, 1和2 就会同步上去 这时候顶部坐标移动到2 while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { //忽略指定的旧元素 final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]); final Widget newWidget = newWidgets[newChildrenTop]; //如果新老相同节点的小部件,类型不一致 直接终止循环 ,那头部扫描只包含之前的扫描的 if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) break; //每单个元素再次递归调更新自己的子部件,最后返回新的元素 final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!; //新的子部件给值 newChildren[newChildrenTop] = newChild; //上一个子元素 previousChild = newChild; newChildrenTop += 1; oldChildrenTop += 1; }
//从底部遍历列表,不同步节点,直到你没有不再有匹配的节点 比如 老的1 2 3 4 5 新的 1 2 7 8 5 这时候倒叙循环。 循环到 4的时候就会停止 4和8不一致。 5不会同步上去,但是底部的下标会移动到 3 while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]); final Widget newWidget = newWidgets[newChildrenBottom]; if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) break; oldChildrenBottom -= 1; newChildrenBottom -= 1; }
//遍历旧列表的变窄部分,得到列表 键和同步 null 与非键项 //剩下来的 顶部2 底部3 开始循环2次 final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom; Map<Key, Element>? oldKeyedChildren; if (haveOldChildren) { oldKeyedChildren = <Key, Element>{}; while (oldChildrenTop <= oldChildrenBottom) { final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]); if (oldChild != null) { if (oldChild.widget.key != null) //缓存旧的有key的部件, 不走deactivate oldKeyedChildren[oldChild.widget.key!] = oldChild; else //移除部件 (调用了生命周期的deactivate ) //里面会移除渲染树, 并添加当前元素到非活动元素集合 deactivateChild(oldChild); } //下标移动 oldChildrenTop += 1; } }
//新的 顶部还是2 底部还是3 //向前移动新列表的缩小部分 while (newChildrenTop <= newChildrenBottom) { Element? oldChild; //获取部件 final Widget newWidget = newWidgets[newChildrenTop]; if (haveOldChildren) { final Key? key = newWidget.key; if (key != null) { //获取旧的缓存对应的key的部件 oldChild = oldKeyedChildren![key]; if (oldChild != null) { if (Widget.canUpdate(oldChild.widget, newWidget)) { //移除缓存 oldKeyedChildren.remove(key); } else { //类型不一样 oldChild = null; } } } } //旧部件和新部件更新 //slotFor 表示上一个元素的位置和值 final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!; //更新对应的新部件 newChildren[newChildrenTop] = newChild; //更新上一个 previousChild = newChild; //下标移动 newChildrenTop += 1; }
//这时候就把最开始底部移动的下标恢复 newChildrenBottom = newWidgets.length - 1; oldChildrenBottom = oldChildren.length - 1; //再次遍历列表底部,同步节点 while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { final Element oldChild = oldChildren[oldChildrenTop]; final Widget newWidget = newWidgets[newChildrenTop]; final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!; newChildren[newChildrenTop] = newChild; previousChild = newChild; newChildrenTop += 1; oldChildrenTop += 1; }
//同步缓存里面还剩下的 //forgottenChildren 表示忽略的元素 if (haveOldChildren && oldKeyedChildren!.isNotEmpty) { for (final Element oldChild in oldKeyedChildren.values) { if (forgottenChildren == null || !forgottenChildren.contains(oldChild)) deactivateChild(oldChild); } } assert(newChildren.every((Element element) => element is! _NullElement)); return newChildren;
生命周期9-此时,框架可能会将此子树重新插入树的另一部分
参考上一个生命周期, 多个子元素组件, 带keuy子元素只是替换的位置, 就不会触发deactivate
生命周期10-如果框架在当前动画帧结束时没有重新插入这个子树,框架将调用dispose,这表明这个State对象永远不会再次构建
void drawFrame() { try { if (renderViewElement != null) buildOwner!.buildScope(renderViewElement!); super.drawFrame(); //通过卸载任何不存在的元素来完成元素构建过程 buildOwner!.finalizeTree(); } }
void finalizeTree() { if (!kReleaseMode) { Timeline.startSync('FINALIZE TREE', arguments: timelineArgumentsIndicatingLandmarkEvent); } try { //建立一个禁止调用 [State.setState] 的范围 并且回调 //这时候就会调用非活动元素集合 lockState(_inactiveElements._unmountAll);...其他省略
void _unmountAll() { _locked = true; final List<Element> elements = _elements.toList()..sort(Element._sort); _elements.clear(); try { //开始循环 _unmount elements.reversed.forEach(_unmount); } finally { _locked = false; } }
void _unmount(Element element) element.visitChildren((Element child) { _unmount(child); }); //开始分离 element.unmount(); }
StatefulElement
@override void unmount() { super.unmount(); //调用dispose state.dispose(); state._element = null; _state = null; }