-
Notifications
You must be signed in to change notification settings - Fork 1
State的生命周期详解
深究一下比如:为什么这些生命周期是如何被回调?图中说的“组件状态改变”调用didUpdateWidget()是什么状态改变?
如图,是一个极为简单的demo,页面一开始展示一个Text(我是第一次build的Text)。下方有一个按钮,点击之后调用setState()切换到SecondWidget。
而第SecondWidget是一个StatefulWidget,里面只是简单的返回了一个Text( 我是被包裹在Stateful中的Text)。我们以setState()过程为例关注SecondWidget的生命周期,分为三种情况。
当我们第一次点击按钮调用setState()的时候,直接来看其实只是相当于将Container下面的child由Text更改为SecondWidget。
调用setState()之后其实会对从当前节点开始,他的所有子孙节点调用updateChild(Element child, Widget newWidget, dynamic newSlot)。
总的来说,这个方法会根据之前挂载在UI树上的_child以及再次调用build()出来的newWidget对象,共有四种情况
-
如果之前的位置child为null
A、如果newWidget为null的话,说明这个位置始终没有子节点,直接返回null即可。 B、如果newWidget不为null,说明这个位置新增加了子节点调用inflateWidget(newWidget, newSlot)生成一个新的Element返回
-
如果之前的child不为null
C、如果newWidget为null的话,说明这个位置需要移除以前的节点,调用deactivateChild(child)移除并且返回null D、如果newWidget不为null的话,先调用Widget.canUpdate(child.widget, newWidget)对比是否能更新。这个方法会对比两个Widget的runtimeType和key,1、如果一致则说明子Widget没有改变,只是需要根据newWidget(配置清单)更新下当前节点的数据child.update(newWidget);2、如果不一致说明这个位置发生变化,则deactivateChild(child)后返回inflateWidget(newWidget, newSlot)
对应到我们的demo中,对于Container而言,在调用setState()之前,他的child是Text所以满足不为空的条件,而setState将child改为了SecondWidget,但是Text和新生成的SecondWidget并非同一种类型,所以会走到条件D的case2中,执行两个流程1、Text的deactivateChild(child);2、SecondWidget的inflateWidget(newWidget, newSlot),我们重点关注SecondWidget。
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
//创建一个element对象
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
一开始会先根据SecondWidget创建一个Element对象,之后调用newChild.mount(this, newSlot)。
@override
void mount(Element parent, dynamic newSlot) {
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
_firstBuild();
}
mount()这个过程即将widget插入UI树中,做一些标志位的赋值,之后调用_firstBuild()。
@override
void _firstBuild() {
assert(_state._debugLifecycleState == _StateLifecycle.created);
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
//首先调用initState()
final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic;
}());
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
//其次调用didChangeDependencies()
_state.didChangeDependencies();
assert(() {
_state._debugLifecycleState = _StateLifecycle.ready;
return true;
}());
//在super中执行当前state的build
super._firstBuild();
}
这个方法在StateElement中被重写,是生命周期的关键所在。如字面意义,他表示第一次构建的时候调用的方法。在这个过程中我们清晰的看出State对象会经历三个回调:
1、_state.initState()
2、_state.didChangeDependencies()
3、 super._firstBuild()最终调用state.build(this)
这个过程结束之后SecondWidget生成的Element对象已经被我们插入到了UI树中,之后渲染流程中,将其展示到屏幕上。
总结:当我们一个StatefulWidget第一次被渲染到屏幕上时,在State中会经历initState(),didChangeDependencies(),build(BuildContext context)三个方法。
假如现在SecondWidget已经被渲染到屏幕上了之后,如果我们再次点击调用setState()。这时会发现,对于Container节点而言,他的child不为空,且始终都是SecondWidget,并且由于我们没指定key对象,所以Widget.canUpdate(child.widget, newWidget)是返回true,对应上面条件D的case1执行child.update(newWidget)
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
_dirty = true;
_state._widget = widget;
try {
//1、先调用didUpdateWidget(oldWidget)
final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
//2、调用rebuild()
rebuild();
}
这个过程也非常清晰,在第二次调用setState()中,state会经历两个回调:
1、_state.didUpdateWidget(oldWidget)
2、state.build(this)
但不仅如此,如果在SecondWidget中使用dependOnInheritedWidgetOfExactType()方法依赖了来自顶层的InheritedWidget数据之时。如果InheritedWidget发生了update(),会先调用所有依赖这个InheritedWidget对象中的didChangeDependencies()(详情可以学习InheritedWidget的依赖更新机制)。并且由于当前的widget一般作为子节点,所以也会执行上面的update()过程
总结:
如果第二次调用setState(),当前的state对象会走:1、didUpdateWidget();2、build()如果是依赖的顶层InheritedWidget发生了改变,则会调用1、didChangeDependencies(); 2、didUpdateWidget();3、build()
第三种情况可以参考情况一种被移除的Text对象,我们提到,当一个State被移除的时候会调用其deactivateChild(Element child)方法
@protected
void deactivateChild(Element child) {
child._parent = null;
child.detachRenderObject();
owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}
这个方法会将当前的对象添加到一个_inactiveElements集合中,并且最终调用deactivate()。
之后在下一帧绘制回调到finalizeTree()的时候,执行unmount()彻底清理
@override
void unmount() {
super.unmount();
_state.dispose();
_state._element = null;
_state = null;
}
总结:
当当前的Widget从屏幕移除的时候回先调用deactivate(),之后在下一帧绘制之前清理UI树的时候被彻底清理,回调dispose()
state的生命周期其实可以用这么一张图理解:
- 1、第一次展示到屏幕上时会依次调用当前element的构造函数,initState,didChangeDependencies,build
- 2、如果只是自己发生了更新,则只会回调build。如果当前对象的父节点发生更新,则会调用didUpdateWidget和build。如果依赖的InheritedWidget发生了改变,则还会先回调didChangeDependencies。
- 3、当widget被移除的的时候,会依次调用deactive和dispose