Flutter 学习笔记: 生命周期
StatelessWidget
StatelessWidget 是无状态的 Widget,一旦创建就不会发生变化,所以无法提供 setState 修改组件的状态,它内部属性应声明为 final,防止意外发生改变。所以 StatelessWidget 的生命周期只有一个,就是 build,build 是用来创建 Widget 的,但因为 build 在每次界面刷新的时候都会调用,所以不要在 build 里写业务逻辑,可以把业务逻辑写到你的 StatelessWidget 的构造函数里。其生命周期如下图:
StatefulWidget
StatefulWidget 是有状态的 Widget,它的 state 在发生变化时会重新渲染 UI,提供 setState 方法修改组件的状态,它的生命周期主要在 State 这块,其生命周期如下图:
下面解释下各个函数:
- initState:当 Widget 第一次插入到 Widget 树时会被调用;对于每一个 State 对象,Flutter framework 只会调用一次该回调,所以,**通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等 **。不能在该回调中调用 BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在 Widget 树上获取离当前 widget 最近的一个父级 InheritFromWidget),原因是在初始化完成后,Widget 树中的 InheritFromWidget 也可能会发生变化,所以正确的做法应该在在 build 方法或 didChangeDependencies 中调用它。
- didChangeDependencies():当 State 对象的依赖发生变化时会被调用;比如其所依赖的 InheritedWidget 发生变化时, Framework 会调用此方法通知组件发生变化。典型的场景是当系统语言 Locale 或应用主题改变时,Flutter framework 会通知 widget 调用此回调。
注意:didChangeDependencies 方法调用后,组件的状态变为 dirty,立即调用 build 方法。
- build:用于构建 Widget 的,会在如下场景被调用:
- 在调用 initState()之后。
- 在调用 didUpdateWidget()之后。
- 在调用 setState()之后。
- 在调用 didChangeDependencies()之后。
- 在 State 对象从树中一个位置移除后(会调用 deactivate)又重新插入到树的其它位置之后。
注意:此方法中应该只包含构建组件的代码,不应该包含其他额外的功能,尤其是耗时任务。
- reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在 Release 模式下永远不会被调用。
- didUpdateWidget():在父 widget 重新构建子 widget 时,子 widget 的 didUpdateWidget 可能会被调用。 之所以说可能,是因为在父 widget 重新构建时,Flutter framework 会调用 Widget.canUpdate 来检测 Widget 树中同一位置的新旧节点的 key 和 runtimeType 是否同时相等,新旧 widget 的 key 和 runtimeType 同时相等时会返回 true,此时 didUpdateWidget()就会被调用,如果新旧节点的 key 和 runtimeType 没有同时相等,didUpdateWidget()不会被调用。
注意:Framework 调用完此方法后,会将组件设置为 dirty 状态,然后调用 build 方法。
- deactivate():当 State 对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework 会将 State 对象重新插到树中,如包含此 State 对象的子树在树的一个位置移动到另一个位置时(可以通过 GlobalKey 来实现)。如果移除后没有重新插入到树中则紧接着会调用 dispose()方法。
- dispose():当 State 对象从树中被永久移除时调用;通常在此回调中释放资源。
实例展示
展示随机数的一个 demo,点击随机按钮,更新随机数,打印此 demo 的生命周期函数的调用流程,代码如下:
dart
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: Scaffold(
body: RandomPage(),
),
);
}
}
class RandomPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _RandomPageState();
}
}
class _RandomPageState extends State<RandomPage> {
final _randomBuild = Random();
int _randomValue = 0;
@override
void initState() {
// TODO: implement initState
super.initState();
print('initState');
}
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
print('didChangeDependencies');
}
@override
Widget build(BuildContext context) {
print('build');
// TODO: implement build
return Container(
child: Padding(
padding: EdgeInsets.only(top: 100),
child: Center(
child: Column(
children: [
Text('随机数: $_randomValue'),
SizedBox(height: 200),
TextButton(
child: Text('切换随机数'),
onPressed: () {
setState(() {
_randomValue = _randomBuild.nextInt(10000);
});
},
)
],
),
),
),
);
}
@override
void reassemble() {
// TODO: implement reassemble
super.reassemble();
print('reassemble');
}
@override
void didUpdateWidget(covariant RandomPage oldWidget) {
// TODO: implement didUpdateWidget
super.didUpdateWidget(oldWidget);
print('didUpdateWidget');
}
@override
void deactivate() {
// TODO: implement deactivate
super.deactivate();
print('deactivate');
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
print('dispose');
}
}
点击 run 首次启动,打印 log 如下:
sh
flutter: initState
flutter: didChangeDependencies
flutter: build
点击切换随机数按钮,打印 log 如下:
sh
flutter: build
点击切换随机数按钮,会调用 setState(),setState 会调用 build 重新构建 widget。
点击 hot reload 按钮,打印 log 如下:
sh
flutter: reassemble
flutter: didUpdateWidget
flutter: build
App 生命周期
通过 WidgetsBindingObserver 的 didChangeAppLifecycleState 可以获取 App 的生命周期状态。生命周期在 AppLifecycleState 类中。常用状态包含如下几个:
- resumed:处于可见并能响应用户的输入
- inactive:处于不活跃状态且无法处理用户的响应
- paused:处于不可见且不能响应用户的输入,但是在后台继续活跃
举例如下:
dart
class _RandomPageState extends State<RandomPage> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance?.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
void didChangeAppLifecycleState(AppLifecycleState state) async {
if (state == AppLifecycleState.resumed) {
getData();
}
}
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
void getData() {
}
}
记得注册和移除监听。
生命周期相关的一些注意点
addPostFrameCallback
页面渲染完毕回调 addPostFrameCallback 是 StatefulWidge 渲染结束的回调,只会被调用一次,之后 StatefulWidget 刷新 UI 也不会被调用。在 initState 不能调用 BuildContext.dependOnInheritedWidgetOfExactType,可以使用 addPostFrameCallback 规避此限制:
dart
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => {});
}
mounted
mounted 是 State 对象中的一个属性,此属性表示当前组件是否在树中(在创建 State 之后,调用 initState 之前,Framework 会将 State 和 BuildContext 进行关联),当 Framework 调用 dispose 时,mounted 被设置为 false,表示当前组件已经不在树中。
createState 函数执行完毕后表示当前组件已经在组件树中,属性 mounted 被 Framework 设置为 true,平时写代码时或者看其他开源代码时经常看到如下代码:
dart
if(mounted){
setState(() { ... });
}
强烈建议:在调用 setState 时加上 mounted 判断。
为什么要加上如此判断?因为如果当前组件未插入到树中或者已经从树中移除时,调用 setState 会抛出异常,加上 mounted 判断,则表示当前组件在树中。
dirty 和 clean
dirty 表示组件当前的状态为 脏状态,下一帧时将会执行 build 函数,调用 setState 方法或者 执行 didUpdateWidget 方法后,组件的状态为 dirty。 clean 与 dirty 相对应,clean 表示组件当前的状态为干净状态,clean 状态下组件不会执行 build 函数。