flutter 生命周期源码解析
状态对象具有以下生命周期:
- 框架通过调用 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 绑定到了statestate._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!;}@overridebool 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 ]) { //其他省略只显示关键代码 //前面会找出脏元素还是重新buildtry { //脏元素重新buildelement.rebuild();} }
Element
@pragma('vm:prefer-inline') void rebuild() { //隐藏其他代码 //开始执行performRebuild(); }
ComponentElement
void performRebuild() {Widget? built;try {//开始获取build 获取新的widgetbuilt = 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 {//开始调用didUpdateWidgetfinal 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的部件, 不走deactivateoldKeyedChildren[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 { //开始循环 _unmountelements.reversed.forEach(_unmount);} finally {_locked = false;} }
void _unmount(Element element) element.visitChildren((Element child) {_unmount(child);}); //开始分离element.unmount(); }
StatefulElement
@override void unmount() {super.unmount(); //调用disposestate.dispose();state._element = null;_state = null; }
flutter 生命周期源码解析相关推荐
- spring源码分析02-spring生命周期源码解析
spring生命周期流程图: 1.spring扫描 Spring最重要的功能就是帮助程序员创建对象(也就是IOC),而启动Spring就是为创建Bean对象 做准备,所以我们先明白Spring到底是怎 ...
- Activity与调用线(三):Activity生命周期源码解析
前言 很高兴遇见你~ 欢迎阅读我的文章. 关于Activity生命周期的文章,网络上真的很多,有很多的博客也都讲得相当不错,可见Activity的重要性是非常高的.事实上,我猜测每个android开发 ...
- 【Spring】Bean生命周期源码分析 总结
[Spring]Bean生命周期源码总结 1.案例验证 定义两个bean A,B 以及实现MyBeanFactoryProcess,MyBeanProcessor,MyInstantiationAwa ...
- 【安卓 R 源码】Activity 启动流程及其生命周期源码分析
1. Activty 的生命周期 activity的生命周期 oncreate()->onstart()->onResume()->onPause()->onStop()-&g ...
- Vue源码解析(一)
前言:接触vue已经有一段时间了,前面也写了几篇关于vue全家桶的内容,感兴趣的小伙伴可以去看看,刚接触的时候就想去膜拜一下源码~可每次鼓起勇气去看vue源码的时候,当看到几万行代码的时候就直接望而却 ...
- Spring Bean的生命周期以及IOC源码解析
IOC源码这一块太多只能讲个大概吧,建议还是去买本Spring IOC源码解析的书来看比较好,我也是自己看源代码以及视频整理的笔记 Bean的生命周期大概可以分为四个阶段,具体的等会再说,先看看IOC ...
- Glide 源码解析之监听生命周期
code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群 作者:断了谁的弦 链接:https://www.jianshu.com/p/1169a91342a9 声明:本文已获断 ...
- Flutter 路由源码解析
前言 这一次,我尝试以不贴一行源代码的方式向你介绍 Flutter 路由的实现原理,同时为了提高你阅读源码的积极性,除了原理介绍以外,又补充了两个新的模块:从源码中学习到的编程技巧,以及 阅读源码之后 ...
- Framework 源码解析知识梳理(5) startService 源码分析
一.前言 最近在看关于插件化的知识,遇到了如何实现Service插件化的问题,因此,先学习一下Service内部的实现原理,这里面会涉及到应用进程和ActivityManagerService的通信, ...
- HandlerThread和IntentService源码解析
简介 首先我们先来了解HandlerThread和IntentService是什么,以及为什么要将这两者放在一起分析. HandlerThread: HandlerThread 其实是Handler ...
最新文章
- 应用丨其实,你每天都生活在人工智能中
- 数聚新动能 数创大未来——2016中国国际大数据大会
- PoseNet: A Convolutional Network for Real-Time 6-DOF Camera Relocalization
- ubuntu 1404部署tomcat7
- android duiqi文字底部,Android中的文本/布局对齐(textAlignment,gravity)
- F - Good Words
- Swift - 图片去色 图片灰色显示
- 【Audio】WAV音频文件格式结构解析
- php与jpython-在python中复数怎么表示
- android----面试基础概括总结
- 基于单片机的超市储物柜设计_基于80C51单片机的电子储物柜系统
- 通过GCN来实现对Cora数据集节点的分类
- odoo中关于打印word格式的文件,利用docxtemplate方法
- java事件处理入门
- 数值分析快速复习(1)——Matlab数值积分
- 软件工程师应具备什么样的素质
- mysql 对账语句_关于对账的一些理解
- java学习顺序(学习路线图)
- 【每日新闻】北京明起将全面取消手机一卡通开卡费
- Labview从入门到会用(一)——创建数据文件