Flutter Widget生命周期

原来我一直在错误的使用 setState()?

State的生命周期

1.Widget的生命周期(1)

initState

当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在Widget树上获取离当前widget最近的一个父级InheritFromWidget,关于InheritedWidget我们将在后面章节介绍),原因是在初始化完成后,Widget树中的InheritFromWidget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。

didChangeDependencies()

当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个InheritedWidget,然后在之后的build() 中InheritedWidget发生了变化,那么此时InheritedWidget的子widget的didChangeDependencies()回调都会被调用。典型的场景是当系统语言Locale或应用主题改变时,Flutter framework会通知widget调用此回调。

build()

它主要是用于构建Widget子树的,会在如下场景被调用:
在调用initState()之后。
在调用didUpdateWidget()之后。
在调用setState()之后。
在调用didChangeDependencies()之后。
在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。

reassemble()

此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。

didUpdateWidget()

在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。正如之前所述,Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。

deactivate()

当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。

dispose()

当State对象从树中被永久移除时调用;通常在此回调中释放资源。

2.Widget的生命周期(2)

  • 初始化(插入渲染树)
  • 状态改变(在渲染树中存在)
  • 销毁(从渲染树种移除)

State 初始化时会依次执行 :构造方法 -> initState -> didChangeDependencies -> build,随后完成页面渲染。

构造函数

用处:

  • 页面传参

  • 初始化UI配置

    构造方法是 State 生命周期的起点,Flutter 会通过调用 StatefulWidget.createState() 来创建一个 State。我们可以通过构造方法,来接收父 Widget 传递的初始化 UI 配置数据。这些配置数据,决定了 Widget 最初的呈现效果。

    这个函数严格意义上来讲不属于生命周期的一部分,因为这个时候State的widget属性为空,无法在构造函数中访问widget的属性 。但是构造函数必然是要第一个调用的。可以在这一部分接收前一个页面传递过来的数据。

initState

用处:

  • 初始化变量

    当State 对象被插入渲染树的时候调用,这个函数在生命周期中只调用一次。所以我们可以在这里做一些初始化工作,比如初始化State的变量、状态变量设定默认值。

didChangeDependencies

  • 初始化时,在initState()之后立刻调用
  • 当依赖的InheritedWidget rebuild,会触发此接口被调用

这个函数会紧跟在initState之后调用,并且可以调用BuildContext.inheritFromWidgetOfExactType

build

作用是构建视图。经过以上步骤,Framework 认为 State 已经准备好了,于是调用 build。我们需要在这个函数中,根据父 Widget 传递过来的初始化配置数据,以及 State 的当前状态,创建一个 Widget 然后返回。

Widget 的状态更新,主要由 3 个方法触发:

  • setState
  • didchangeDependencies
  • didUpdateWidget

setState

我们最熟悉的方法之一。当状态数据发生变化时,我们总是通过调用这个方法告诉 Flutter:“我这儿的数据变啦,请使用更新后的数据重建 UI!”;当setState触发的时候build会再次被调用。

addPostFrameCallback

addPostFrameCallback是StatefulWidget渲染结束之后的回调,只会调用一次,一般是在initState里添加回调:

1
2
3
4
5
6
import 'package:flutter/scheduler.dart';
@override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) => {});
}

didUpdateWidget

当组件的状态改变的时候就会调用didUpdateWidget,比如调用了setState.
实际上这里flutter框架会创建一个新的Widget,绑定本State,并在这个函数中传递老的Widget。
这个函数一般用于比较新、老Widget,看看哪些属性改变了,并对State做一些调整。
需要注意的是,涉及到controller的变更,需要在这个函数中移除老的controller的监听,并创建新controller的监听。

didChangeDependencies

State 对象的依赖关系发生变化后,Flutter 会回调这个方法,随后触发组件构建。哪些情况下 State 对象的依赖关系会发生变化呢?典型的场景是,系统语言 Locale 或应用主题改变时,系统会通知 State 执行 didChangeDependencies 回调方法。

一旦这三个方法被调用,Flutter 随后就会销毁老 Widget,并调用 build 方法重建 Widget。

deactivate

在dispose之前,会调用这个函数。实测在组件可见状态变化的时候会调用,当组件卸载时也会先一步dispose调用。值得注意的是,页面切换时,由于 State 对象在视图树中的位置发生了变化,需要先暂时移除后再重新添加,重新触发组件构建,因此这个函数也会被调用。

dispose

一旦到这个阶段,组件就要被销毁了,这个函数一般会移除监听,清理环境。

1
2
3
4
5
@override
void dispose() {
super.dispose();
_controller.dispose();
}
阶段 调用次数 是否支持setState
构造函数 1
initState 1 无效(使用setState和不使用一样)
didChangeDependencies >=1 无效
didUpdateWidget >=1
build >=1
deactivate >=1
dispose 1

3.Widget的生命周期(3)


Flutter生命周期可以分为3个阶段:

1、实例化组件并添加到树, 即Navigator.push;

2、状态变化,即打开新的widget或者依赖的上级widget发生变化;

3、从树中移除, 即Navigator.pop。

在Flutter中Widget都是不可变的, 但实际上需要根据对应的状态刷新Widget。 从而产生了StatelessWidget和StatefulWdiget, StatefulWidget是由2个对象Widget和State组成的。

为什么将State和Widget分开呢?

答案是性能, State管理状态(可以理解为Controller),Widget是UI(即View)。 根据状态变化每次生成Widget(即View)可以节省内存,即不必每次创建状态对象State。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 初始化状态,只执行一次
void initState() { }

// 当widget依赖的对象发生变化时调用,例如全局的主题或语言
void didChangeDependencies() { }

// 当widget重新构建时调用,根据canUpdate方法判断
void didUpdateWidget(covariant T oldWidget) { }

// 构建UI
void build() { }

// 调试模式下,热重载执行,Release模式不会执行
void reassemble() { }

// 当widget被移除时调用
void deactivate() { }

// 当widget被永久移除时,可以做释放资源
void dispose() { }

APP的生命周期

在原生开发中,我们可以通过重写 Activity、ViewController 生命周期回调方法,或是注册应用程序的相关通知,来监听 App 的生命周期并做相应的处理。而在 Flutter 中,我们可以利用 WidgetsBindingObserver 类,来实现同样的需求。

通过继承WidgetsBindingObserver,该类中存在一个回调方法didChangeAppLifecycleState,用于表示当前应用状态改变。(当然WidgetsBindingObserver 中还有其他很多状态回调,比如一个route 被push 或者pop,比如横竖屏变化,比如用户locales切换,是否存在内存压力,辅助功能切换等。)

AppLifecycleState就是App的生命周期,包含四个:

  • resumed:可见并能响应用户的输入
  • inactive:处在并不活动状态,无法处理用户响应
  • paused:不可见并不能响应用户的输入,但是在后台继续活动中
  • suspending
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
abstract class WidgetsBindingObserver {
//页面pop
Future<bool> didPopRoute() => Future<bool>.value(false);
//页面push
Future<bool> didPushRoute(String route) => Future<bool>.value(false);
//系统窗口相关改变回调,如旋转
void didChangeMetrics() { }
//文本缩放系数变化
void didChangeTextScaleFactor() { }
//系统亮度变化
void didChangePlatformBrightness() { }
//本地化语言变化
void didChangeLocales(List<Locale> locale) { }
//App生命周期变化
void didChangeAppLifecycleState(AppLifecycleState state) { }
//内存警告回调
void didHaveMemoryPressure() { }
//Accessibility相关特性回调
void didChangeAccessibilityFeatures() {}
}

实践

代码

main.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import 'package:flutter/material.dart';
import 'package:lifecyle/screen_two.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
int _counter = 0;

void _incrementCounter() {
setState(() {
_counter++;
});
}

//当Widget第一次插入到Widget树时会被调用。
//对于每一个State对象,Flutter只会调用该回调一次
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
print("one initState");
}

//初始化时,在initState之后立刻调用
//当State的依赖关系发生变化时,会触发此接口被调用
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("one didChangeDependencies");
}

//状态改变的时候会调用该方法,比如父类调用了setState
@override
void didUpdateWidget(MyHomePage oldWidget) {
super.didUpdateWidget(oldWidget);
print("one didUpdateWidget");
}

//监听App生命周期回调
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print('one $state');
}

@override
void reassemble() {
super.reassemble();
print("one reassemble");
}

//当State对象从树中被移除时,会调用此回调
@override
void deactivate() {
super.deactivate();
print('one deactivate');
}

//当State对象从树中被永久移除时调用;通常在此回调中释放资源
@override
void dispose() {
super.dispose();
WidgetsBinding.instance!.removeObserver(this);
print("one dispose");
}

@override
Widget build(BuildContext context) {
print("one build");
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) => const ScreenTwo(),
),
);
},
child: Text('Screen two'),
),
Text('Minimize and check console or goto screen two ')
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

screen_two.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import 'package:flutter/material.dart';

class ScreenTwo extends StatefulWidget {
const ScreenTwo({Key? key}) : super(key: key);

@override
_ScreenTwoState createState() => _ScreenTwoState();
}

class _ScreenTwoState extends State<ScreenTwo> with WidgetsBindingObserver {
//当Widget第一次插入到Widget树时会被调用。
//对于每一个State对象,Flutter只会调用该回调一次
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
print("two initState");
}

//初始化时,在initState之后立刻调用
//当State的依赖关系发生变化时,会触发此接口被调用
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("two didChangeDependencies");
}

//状态改变的时候会调用该方法,比如父类调用了setState
@override
void didUpdateWidget(ScreenTwo oldWidget) {
super.didUpdateWidget(oldWidget);
print("two didUpdateWidget");
}

//监听App生命周期回调
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print('two $state');
}

@override
void reassemble() {
super.reassemble();
print("two reassemble");
}

//当State对象从树中被移除时,会调用此回调
@override
void deactivate() {
super.deactivate();
print('two deactivate');
}

//当State对象从树中被永久移除时调用;通常在此回调中释放资源
@override
void dispose() {
super.dispose();
WidgetsBinding.instance!.removeObserver(this);
print("two dispose");
}

@override
Widget build(BuildContext context) {
print("two build");
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text('Minimize and check console'),
),
);
}
}

基本场景

miui 12.5.3

android 11

1. 初始化

打开一个新页面

1
2
3
I/flutter (32618): one initState
I/flutter (32618): one didChangeDependencies
I/flutter (32618): one build

2. 页面数据更新

页面按钮点击或弹框处理,均不会涉及生命周期变化,只是在 build 更新 Widget 资源

1
I/flutter (32618): one build

但是若进行 热重载 生命周期如下

1
2
3
I/flutter (32618): one reassemble
I/flutter (32618): one didUpdateWidget
I/flutter (32618): one build

如果处于第二个页面的时候热重载

1
2
3
4
5
I/flutter ( 2535): one reassemble
I/flutter ( 2535): two reassemble
I/flutter ( 2535): one didUpdateWidget
I/flutter ( 2535): one build
I/flutter ( 2535): two build

3. 横竖屏切换

不会涉及生命周期变化

4. 切至后台

1
2
I/flutter (32618): one AppLifecycleState.inactive
I/flutter (32618): one AppLifecycleState.paused

5. 切回前台

1
I/flutter (32618): one AppLifecycleState.resumed

6. 销毁页面

打开第二页面然后在退出

1
2
I/flutter ( 2535): two deactivate
I/flutter ( 2535): two dispose

进阶场景

7. 打开新的页面

1
2
3
I/flutter ( 2535): two initState
I/flutter ( 2535): two didChangeDependencies
I/flutter ( 2535): two build

8. 新页面切至后台(旧页面未销毁)

1
2
3
4
I/flutter ( 2535): one AppLifecycleState.inactive
I/flutter ( 2535): two AppLifecycleState.inactive
I/flutter ( 2535): one AppLifecycleState.paused
I/flutter ( 2535): two AppLifecycleState.paused

9. 新页面切回前台

1
2
I/flutter ( 2535): one AppLifecycleState.resumed
I/flutter ( 2535): two AppLifecycleState.resumed

11.进入后台卡片页面

1
2
I/flutter ( 2535): one AppLifecycleState.inactive
I/flutter ( 2535): one AppLifecycleState.paused

处于第二个页面时进入后台

1
2
3
4
I/flutter ( 2535): one AppLifecycleState.inactive
I/flutter ( 2535): two AppLifecycleState.inactive
I/flutter ( 2535): one AppLifecycleState.paused
I/flutter ( 2535): two AppLifecycleState.paused

12. 进行分屏

1
2
3
4
5
I/flutter ( 2535): one AppLifecycleState.inactive
I/flutter ( 2535): one AppLifecycleState.paused
I/flutter ( 2535): one AppLifecycleState.resumed
I/flutter ( 2535): one AppLifecycleState.inactive
I/flutter ( 2535): one AppLifecycleState.resumed

如果处于第二个页面的时候分屏

1
2
3
4
5
6
7
8
9
10
I/flutter ( 2535): one AppLifecycleState.inactive
I/flutter ( 2535): two AppLifecycleState.inactive
I/flutter ( 2535): one AppLifecycleState.paused
I/flutter ( 2535): two AppLifecycleState.paused
I/flutter ( 2535): one AppLifecycleState.resumed
I/flutter ( 2535): two AppLifecycleState.resumed
I/flutter ( 2535): one AppLifecycleState.inactive
I/flutter ( 2535): two AppLifecycleState.inactive
I/flutter ( 2535): one AppLifecycleState.resumed
I/flutter ( 2535): two AppLifecycleState.resumed

13.锁屏

1
2
3
4
5
I/flutter (32618): one AppLifecycleState.inactive
I/flutter (32618): one AppLifecycleState.paused
I/flutter (32618): one AppLifecycleState.resumed
I/flutter (32618): one AppLifecycleState.inactive
I/flutter (32618): one AppLifecycleState.paused

如果处于第二个页面的时候锁屏

1
2
3
4
5
6
7
8
9
10
I/flutter ( 2535): one AppLifecycleState.inactive
I/flutter ( 2535): two AppLifecycleState.inactive
I/flutter ( 2535): one AppLifecycleState.paused
I/flutter ( 2535): two AppLifecycleState.paused
I/flutter ( 2535): one AppLifecycleState.resumed
I/flutter ( 2535): two AppLifecycleState.resumed
I/flutter ( 2535): one AppLifecycleState.inactive
I/flutter ( 2535): two AppLifecycleState.inactive
I/flutter ( 2535): one AppLifecycleState.paused
I/flutter ( 2535): two AppLifecycleState.paused

14.解锁

1
I/flutter (32618): one AppLifecycleState.resumed

如果处于第二个页面的时候锁屏在解锁

1
2
I/flutter ( 2535): one AppLifecycleState.resumed
I/flutter ( 2535): two AppLifecycleState.resumed