生命周期
生命周期
Flutter 中有两个主要的 Widget:StatelessWidget(无状态) 和 StatefulWidget(有状态)
StatelessWidget
无状态组件是不可变的,这意味着它们的属性不能变化,所有的值都是最终的。可以理解为将外部传入的数据转化为界面展示的内容,只会渲染一次。 对于无状态组件生命周期只有 build 这个过程。无状态组件的构建方法通常只在三种情况下会被调用:小组件第一次被插入树中,小组件的父组件改变其配置,以及它所依赖的 InheritedWidget 发生变化时。
StatefulWidget
有状态组件持有的状态可能在 Widget 生命周期中发生变化,是定义交互逻辑和业务逻辑。可以理解为具有动态可交互的内容界面,会根据数据的变化进行多次渲染。实现一个 StatefulWidget 至少需要两个类:
- 一个是 StatefulWidget 类。
- 另一个是 Sate 类。StatefulWidget 类本身是不可变的,但是 State 类在 Widget 生命周期中始终存在。StatefulWidget 将其可变的状态存储在由 createState 方法创建的 State 对象中,或者存储在该 State 订阅的对象中。
StatefulWidget 生命周期
createState()
Framework 调用会通过调用 StatefulWidget.createState()
来创建一个 State。
initState()
新创建的 State 会和一个 BuildContext 产生关联,此时认为 State 已经被安装好了,initState()
函数将会被调用。
通常,我们可以重写这个函数,进行初始化操作。
didChangeDependencies()
在 initState()
调用结束后,这个函数会被调用。
事实上,当 State 对象的依赖关系发生变化时,这个函数总会被 Framework 调用。
build()
经过以上步骤,系统认为一个 State 已经准备好了,就会调用 build()
来构建视图。
我们需要在这个函数中,返回一个 Widget。
deactivate()
当 State 被暂时从视图树中移除时,会调用这个函数。
页面切换时,也会调用它,因为此时 State 在视图树中的位置发生了变化,需要先暂时移除后添加。
⚠️注意,重写的时候必须要调用 super.deactivate()
。
dispose()
当 State 被永久的从视图树中移除,Framework 会调用该函数。
在销毁前触发,我们可以在这里进行最终的资源释放。
在调用这个函数之前,总会先调用 deactivate()
。
⚠️注意,重写的时候必须要调用 super.dispose()
。
didUpdateWidget()
当 widget 的配置发生变化时,会调用这个函数。
比如,Hot-reload 的时候就会调用这个函数。
这个函数调用后,会调用 build()
。
setState()
当我需要更新 State 的视图时,需要手动调用这个函数,它会触发 build()
。
大致分为四个阶段
- 初始化阶段,包括两个生命周期函数 createState 和 initState;
- 组件创建阶段,包括 didChangeDependencies 和 build;
- 触发组件多次 build ,这个阶段有可能是因为 didChangeDependencies、 setState 或者 didUpdateWidget 而引发的组件重新 build ,在组件运行过程中会多次触发,这也是优化过程中需要着重注意的点;
- 最后是组件销毁阶段,deactivate 和 dispose。
不是生命周期但是却非常重要的几个概念
下面这些并不是生命周期的一部分,但是在生命周期中起到了很重要的作用。
- mounted:是 State 中的一个重要属性,相当于一个标识,用来表示当前组件是否在树中。在 createState 后 initState 前,mounted 会被置为 true,表示当前组件已经在树中。调用 dispose 时,mounted 被置为 false,表示当前组件不在树中。
- dirty:表示当前组件为脏状态,下一帧时将会执行 build 函数,调用 setState 方法或者执行 didUpdateWidget 方法后,组件的状态为 dirty。
- clean:与 dirty 相对应,clean 表示组件当前的状态为干净状态,clean 状态下组件不会执行 build 函数。

上图为 flutter 生命周期流程图
大致分为四个阶段
- 初始化阶段,包括两个生命周期函数 createState 和 initState;
- 组件创建阶段,包括 didChangeDependencies 和 build;
- 触发组件多次 build ,这个阶段有可能是因为 didChangeDependencies、 setState 或者 didUpdateWidget 而引发的组件重新 build ,在组件运行过程中会多次触发,这也是优化过程中需要着重注意的点;
- 最后是组件销毁阶段,deactivate 和 dispose。
组件首次加载执行过程
首先我们来实现下面这段代码(类似于 flutter 自己的计数器项目),康康组件首次创建是否按照上述流程图中的顺序来执行的。
- 创建一个 flutter 项目;
- 创建 count_widget.dart 中添加以下代码;
import 'package:flutter/material.dart';
class CountWidget extends StatefulWidget {
CountWidget({Key key}) : super(key: key);
@override
_CountWidgetState createState() {
print('count createState');
return _CountWidgetState();
}
}
class _CountWidgetState extends State<CountWidget> {
int _count = 0;
void _incrementCounter() {
setState(() {
print('count setState');
_count++;
});
}
@override
void initState() {
print('count initState');
super.initState();
}
@override
void didChangeDependencies() {
print('count didChangeDependencies');
super.didChangeDependencies();
}
@override
void didUpdateWidget(CountWidget oldWidget) {
print('count didUpdateWidget');
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print('count deactivate');
super.deactivate();
}
@override
void dispose() {
print('count dispose');
super.dispose();
}
@override
void reassemble() {
print('count reassemble');
super.reassemble();
}
@override
Widget build(BuildContext context) {
print('count build');
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$_count',
style: Theme.of(context).textTheme.headline4,
),
Padding(
padding: EdgeInsets.only(top: 100),
child: IconButton(
icon: Icon(
Icons.add,
size: 30,
),
onPressed: _incrementCounter,
),
),
],
),
);
}
}
上述代码把 StatefulWidget 的一些生命周期都进行了重写,并且在执行中都打印了标识,方便看到函数的执行顺序。
- 在 main.dart 中加载该组件。代码如下:
import 'package:flutter/material.dart';
import './pages/count_widget.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: CountWidget(),
);
}
}
这时 CountWidget 作为 MyHomePage 的子组件。我们打开模拟器,开始运行。在控制台可以看到如下日志,可以看出 StatefulWidget 在第一次被创建的时候是调用下面四个函数。
flutter: count createState
flutter: count initState
flutter: count didChangeDependencies
flutter: count build
点击屏幕上的 ➕ 按钮,_count 增加 1,模拟器上的数字由 0 变为 1,日志如下。也就是说在状态发生变化的时候,会调用 setState
和 build
两个函数。
flutter: count setState
flutter: count build
command + s 热重载后,日志如下:
flutter: count reassemble
flutter: count didUpdateWidget
flutter: count build
注释掉 main.dart 中的 CountWidget,command + s 热重载后,这时 CountWidget 消失在模拟器上,日志如下:
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
// body: CountWidget(),
);
}
}
flutter: count reassemble
flutter: count deactivate
flutter: count dispose
经过上述一系列操作之后,通过日志打印并结合生命周期流程图,我们可以很清晰的看出各生命周期函数的作用以及理解生命周期的几个阶段。 相信很多细心的同学已经发现了一个细节,那就是 build
方法在不同的操作中都被调用了,下面我们来介绍什么情况下会触发组件再次 build。
触发组件再次 build
触发组件再次 build 的方式有三种,分别是 setState
、didChangeDependencies
、didUpdateWidget
。
1.setState
很好理解,只要组件状态发生变化时,就会触发组件 build。在上述的操作过程中,点击 ➕ 按钮,_count 会加 1,结果如下图:

2.didChangeDependencies
,组件依赖的全局 state 发生了变化时,也会调用 build。例如系统语言等、主题色等。
3.didUpdateWidget
,我们以下方代码为例。在 main.dart 中,同样的重写生命周期函数,并打印。在 CountWidget 外包一层 Column ,并创建同级的 RaisedButton 做为父 Widget 中的计数器。
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() {
print('main createState');
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
int mainCount = 0;
void _changeMainCount() {
setState(() {
print('main setState');
mainCount++;
});
}
@override
void initState() {
print('main initState');
super.initState();
}
@override
void didChangeDependencies() {
print('main didChangeDependencies');
super.didChangeDependencies();
}
@override
void didUpdateWidget(MyHomePage oldWidget) {
print('main didUpdateWidget');
super.didUpdateWidget(oldWidget);
}
@override
void deactivate() {
print('main deactivate');
super.deactivate();
}
@override
void dispose() {
print('main dispose');
super.dispose();
}
@override
void reassemble() {
print('main reassemble');
super.reassemble();
}
@override
Widget build(BuildContext context) {
print('main build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: <Widget>[
RaisedButton(
onPressed: () => _changeMainCount(),
child: Text('mainCount = $mainCount'),
),
CountWidget(),
],
),
);
}
}
重新加载 app,可以看到打印日志如下:

flutter: main createState
flutter: main initState
flutter: main didChangeDependencies
flutter: main build
flutter: count createState
flutter: count initState
flutter: count didChangeDependencies
flutter: count build
可以发现:
- 父组件也经历了
createState
、initState
、didChangeDependencies
、build
这四个过程。 - 并且父组件要在
build
之后才会创建子组件。
点击 MyHomePage(父组件)的 mainCount 按钮 ,打印如下:
flutter: main setState
flutter: main build
flutter: count didUpdateWidget
flutter: count build
点击 CountWidget 的 ➕ 按钮,打印如下:
flutter: count setState
flutter: count build
可以说明父组件的 State 变化会引起子组件的 didUpdateWidget 和 build,子组件自己的状态变化不会引起父组件的状态改变。
组件销毁
我们重复上面的操作,为 CountWidget 添加一个子组件 CountSubWidget,并用 count sub 前缀打印日志。重新加载 app。
注释掉 CountWidget 中的 CountSubWidget,打印日志如下:
flutter: main reassemble
flutter: count reassemble
flutter: count sub reassemble
flutter: main didUpdateWidget
flutter: main build
flutter: count didUpdateWidget
flutter: count build
flutter: count sub deactivate
flutter: count sub dispose
恢复到注释前,注释掉 MyHomePage 中的 CountWidget,打印如下:
flutter: main reassemble
flutter: count reassemble
flutter: count sub reassemble
flutter: main didUpdateWidget
flutter: main build
flutter: count deactivate
flutter: count sub deactivate
flutter: count sub dispose
flutter: count dispose
因为是热重载,所以会调用 reassemble
、 didUpdateWidget
、 build
,我们可以忽略带有这几个函数的打印日志。可以得出结论: 父组件移除,会先移除节点,然后子组件移除节点,子组件被永久移除,最后是父组件被永久移除。
Flutter App Lifecycle
上面我们介绍的生命周期主要是 StatefulWidget 组件的生命周期,下面我们来简单介绍一下和 app 平台相关的生命周期,比如退出到后台。
我们创建 app_lifecycle_state.dart 文件并创建 AppLifecycle,他是一个 StatefulWidget,但是他要继承 WidgetsBindingObserver。
import 'package:flutter/material.dart';
class AppLifecycle extends StatefulWidget {
AppLifecycle({Key key}) : super(key: key);
@override
_AppLifecycleState createState() {
print('sub createState');
return _AppLifecycleState();
}
}
class _AppLifecycleState extends State<AppLifecycle>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
print('sub initState');
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// TODO: implement didChangeAppLifecycleState
super.didChangeAppLifecycleState(state);
print('didChangeAppLifecycleState');
if (state == AppLifecycleState.resumed) {
print('resumed:');
} else if (state == AppLifecycleState.inactive) {
print('inactive');
} else if (state == AppLifecycleState.paused) {
print('paused');
} else if (state == AppLifecycleState.detached) {
print('detached');
}
}
@override
Widget build(BuildContext context) {
print('sub build');
return Container(
child: Text('data'),
);
}
}
didChangeAppLifecycleState 方法是重点,AppLifecycleState 中的状态包括:resumed、inactive、paused、detached 四种。
didChangeAppLifecycleState 方法的依赖于系统的通知(notifications),正常情况下,App是可以接收到这些通知,但有个别情况下是无法接收到通知的,比如用户关机等。它的四种生命周期状态枚举源码中有详细的介绍和说明。
- resumed:该应用程序是可见的,并对用户的输入作出反应。也就是应用程序进入前台。
- inactive:应用程序处于非活动状态,没有接收用户的输入。在 iOS 上,这种状态对应的是应用程序或 Flutter 主机视图在前台非活动状态下运行。当处于电话呼叫、响应 TouchID 请求、进入应用切换器或控制中心时,或者当 UIViewController 托管的 Flutter 应用程序正在过渡。在 Android 上,这相当于应用程序或 Flutter 主机视图在前台非活动状态下运行。当另一个活动被关注时,如分屏应用、电话呼叫、画中画应用、系统对话框或其他窗口,应用会过渡到这种状态。也就是应用进入后台。
- pause:该应用程序目前对用户不可见,对用户的输入没有反应,并且在后台运行。当应用程序处于这种状态时,引擎将不会调用。也就是说应用进入非活动状态。
- detached:应用程序仍然被托管在flutter引擎上,但与任何主机视图分离。处于此状态的时机:引擎首次加载到附加到一个平台 View 的过程中,或者由于执行 Navigator pop,view 被销毁。
除了 app 生命周期的方法,Flutter 还有一些其他不属于生命周期,但是也会在一些特殊时机被观察到的方法,如 **didChangeAccessibilityFeatures(当前系统改变了一些访问性活动的回调) 、didHaveMemoryPressure(低内存回调)、didChangeLocales(用户本地设置变化时调用,如系统语言改变)、didChangeTextScaleFactor(文字系数变化)**等。