在说 Flutter 生命周期前,先介绍一下 Flutter 的组件,然后是组件的生命周期。

一、组件 Widget

一个 Flutter 组件,包含了组件的模板、样式和交互等内容,外部只要按照组件设定的属性、函数及事件处理等进行调用即可,完全不用考虑组件的内部实现逻辑。其中组件又包括无状态组件和有状态组件。

  • 无状态组件

无状态组件,可以理解为将外部传入的数据转化为界面展示的内容,只会渲染一次。

  • 有状态组件

有状态组件,是定义交互逻辑和业务数据,可以理解为具有动态可交互的内容界面,会根据数据的变化进行多次渲染。

二、生命周期

在原生 Android 、原生 iOS 、前端 React 或者 Vue 都存在生命周期的概念,在 Flutter 中一样存在生命周期的概念,其基本概念和作用相似。 Flutter 中说的生命周期,也是指有状态组件,对于无状态组件生命周期只有 build 这个过程,也只会渲染一次,而有状态组件则比较复杂,下面我们就来看看有状态组件的生命周期过程。

2.1 生命周期的流转

Flutter 中的生命周期,包含以下几个阶段:

  • createState ,该函数为 StatefulWidget 中创建 State 的方法,当 StatefulWidget 被调用时会立即执行 createState 。
  • initState ,该函数为 State 初始化调用,因此可以在此期间执行 State 各变量的初始赋值,同时也可以在此期间与服务端交互,获取服务端数据后调用 setState 来设置 State。
  • didChangeDependencies ,该函数是在该组件依赖的 State 发生变化时,这里说的 State 为全局 State ,例如语言或者主题等,类似于前端 Redux 存储的 State 。
  • build ,主要是返回需要渲染的 Widget ,由于 build 会被调用多次,因此在该函数中只能做返回 Widget 相关逻辑,避免因为执行多次导致状态异常。
  • reassemble ,主要是提供开发阶段使用,在 debug 模式下,每次热重载都会调用该函数,因此在 debug 阶段可以在此期间增加一些 debug 代码,来检查代码问题。
  • didUpdateWidget ,该函数主要是在组件重新构建,比如说热重载,父组件发生 build 的情况下,子组件该方法才会被调用,其次该方法调用之后一定会再调用本组件中的 build 方法。
  • deactivate ,在组件被移除节点后会被调用,如果该组件被移除节点,然后未被插入到其他节点时,则会继续调用 dispose 永久移除。
  • dispose ,永久移除组件,并释放组件资源。

生命周期流程图:

整个过程分为四个阶段:

  1. 初始化阶段,包括两个生命周期函数 createState 和 initState;
  2. 组件创建阶段,也可以称组件出生阶段,包括 didChangeDependencies 和 build;
  3. 触发组件多次 build ,这个阶段有可能是因为 didChangeDependencies、setState 或者 didUpdateWidget 而引发的组件重新 build ,在组件运行过程中会多次被触发,这也是优化过程中需要着重需要注意的点;
  4. 最后是组件销毁阶段,deactivate 和 dispose。

2.1 组件首次加载执行过程

1、 在 lib 中 pages 下创建 test_stateful_widget.dart ;

2、 在 test_stateful_widget.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
import 'package:flutter/material.dart';

/// 创建有状态测试组件
class TestStatefulWidget extends StatefulWidget {
@override
createState() {
print('create state');
return TestState();
}
}

/// 创建状态管理类,继承状态测试组件
class TestState extends State<TestStatefulWidget> {
/// 定义 state [count] 计算器
int count = 1;

/// 定义 state [name] 为当前描述字符串
String name = 'test';

@override
initState() {
print('init state');
super.initState();
}

@override
didChangeDependencies() {
print('did change dependencies');
super.didChangeDependencies();
}

@override
didUpdateWidget(TestStatefulWidget oldWidget) {
count++;
print('did update widget');
super.didUpdateWidget(oldWidget);
}

@override
deactivate() {
print('deactivate');
super.deactivate();
}

@override
dispose() {
print('dispose');
super.dispose();
}

@override
reassemble() {
print('reassemble');
super.reassemble();
}

/// 修改 state name
void changeName() {
setState(() {
print('set state');
this.name = 'flutter';
});
}

@override
Widget build(BuildContext context) {
print('build');
return Column(
children: <Widget>[
FlatButton(
child: Text('$name $count'), // 使用 Text 组件显示描述字符和当前计算
onPressed: () => this.changeName(), // 点击触发修改描述字符 state name
)
],
);
}
}

上述代码把有状态组件的一些生命周期函数都进行了重写,并且在执行中都打印了一些字符串标识,目的是可以看到该函数被执行。

运行程序,在输出控制台可以看到下面的运行打印日志信息。

1
2
3
4
5
6
7
flutter: create state
flutter: init state
flutter: did change dependencies
flutter: build
flutter: reassemble
flutter: did update widget
flutter: build

运行结果中,打印过程可以看到是按照我们上面图 1 的执行流程在运行的,但其中最值得关注的是 build 运行了两次。这是在开发模式下才会执行的过程,在正式环境是不会出现的,因为重新渲染成本非常大,这个问题可以使用打印 build 的调用堆栈即可发现。如果你要关闭两次 build 也可以实现,在 Flutter 框架中搜索 constants.dart 文件,并找到下面这行代码,将 defaultValue 从 false 修改为 true。

1
const bool kReleaseMode = bool.fromEnvironment('dart.vm.product', defaultValue: true);

其实这里会触发 didUpdateWidget 函数,是因为 TestStatefulWidget 组件是 MyApp 组件中的子组件,从而导致 MyApp 函数中的 build 触发子组件 didUpdateWidget 函数的执行;