面试官:知道 Flutter 生命周期?下周来入职!
点击上方↑↑↑“GDG广州”关注我
该文章由社区志愿者袁俊耀推荐
安卓
如果你是一名安卓开发工程师,那么对于 Activity 生命周期肯定不陌生
•onCreate•onStart•onResume•onPause•onStop•onDestroy
iOS
如果你是一名 iOS 开发工程师,那么 UIViewController 的生命周期肯定也已经很了解了。
•viewDidLoad•viewWillAppear•viewDidAppear•viewWillDisappear•viewDidDisappear•viewDidUnload
Flutter
知道了 Android 和 iOS 的生命周期,那么 Flutter 呢?有和移动端对应的生命周期函数么?如果之前你对 Flutter 有一点点了解的话,你会发现 Flutter 中有两个主要的 Widget:StatelessWidget(无状态) 和 StatefulWidget(有状态)。本篇文章我们主要来介绍下 StatefulWidget,因为它有着和 Android 和 iOS 相似的生命周期。
StatelessWidget
无状态组件是不可变的,这意味着它们的属性不能变化,所有的值都是最终的。可以理解为将外部传入的数据转化为界面展示的内容,只会渲染一次。对于无状态组件生命周期只有 build 这个过程。无状态组件的构建方法通常只在三种情况下会被调用:小组件第一次被插入树中,小组件的父组件改变其配置,以及它所依赖的 InheritedWidget 发生变化时。
StatefulWidget
有状态组件持有的状态可能在 Widget 生命周期中发生变化,是定义交互逻辑和业务逻辑。可以理解为具有动态可交互的内容界面,会根据数据的变化进行多次渲染。实现一个 StatefulWidget 至少需要两个类:
•一个是 StatefulWidget 类。•另一个是 Sate 类。StatefulWidget 类本身是不可变的,但是 State 类在 Widget 生命周期中始终存在。StatefulWidget 将其可变的状态存储在由 createState 方法创建的 State 对象中,或者存储在该 State 订阅的对象中。
StatefulWidget 生命周期
•createState:该函数为 StatefulWidget 中创建 State 的方法,当 StatefulWidget 被创建时会立即执行 createState。createState 函数执行完毕后表示当前组件已经在 Widget 树中,此时有一个非常重要的属性 mounted 被置为 true。•initState:该函数为 State 初始化调用,只会被调用一次,因此,通常会在该回调中做一些一次性的操作,如执行 State 各变量的初始赋值、订阅子树的事件通知、与服务端交互,获取服务端数据后调用 setState 来设置 State。•didChangeDependencies:该函数是在该组件依赖的 State 发生变化时会被调用。这里说的 State 为全局 State,例如系统语言 Locale 或者应用主题等,Flutter 框架会通知 widget 调用此回调。类似于前端 Redux 存储的 State。该方法调用后,组件的状态变为 dirty,立即调用 build 方法。•build:主要是返回需要渲染的 Widget,由于 build 会被调用多次,因此在该函数中只能做返回 Widget 相关逻辑,避免因为执行多次而导致状态异常。•reassemble:主要在开发阶段使用,在 debug 模式下,每次热重载都会调用该函数,因此在 debug 阶段可以在此期间增加一些 debug 代码,来检查代码问题。此回调在 release 模式下永远不会被调用。•didUpdateWidget:该函数主要是在组件重新构建,比如说热重载,父组件发生 build 的情况下,子组件该方法才会被调用,其次该方法调用之后一定会再调用本组件中的 build 方法。•deactivate:在组件被移除节点后会被调用,如果该组件被移除节点,然后未被插入到其他节点时,则会继续调用 dispose 永久移除。•dispose:永久移除组件,并释放组件资源。调用完 dispose 后,mounted 属性被设置为 false,也代表组件生命周期的结束。
不是生命周期但是却非常重要的几个概念
下面这些并不是生命周期的一部分,但是在生命周期中起到了很重要的作用。
•mounted:是 State 中的一个重要属性,相当于一个标识,用来表示当前组件是否在树中。在 createState 后 initState 前,mounted 会被置为 true,表示当前组件已经在树中。调用 dispose 时,mounted 被置为 false,表示当前组件不在树中。•dirty:表示当前组件为脏状态,下一帧时将会执行 build 函数,调用 setState 方法或者执行 didUpdateWidget 方法后,组件的状态为 dirty。•clean:与 dirty 相对应,clean 表示组件当前的状态为干净状态,clean 状态下组件不会执行 build 函数。
上图为 flutter 生命周期流程图
大致分为四个阶段
1.初始化阶段,包括两个生命周期函数 createState 和 initState;2.组件创建阶段,包括 didChangeDependencies 和 build;3.触发组件多次 build ,这个阶段有可能是因为 didChangeDependencies、 setState 或者 didUpdateWidget 而引发的组件重新 build ,在组件运行过程中会多次触发,这也是优化过程中需要着重注意的点;4.最后是组件销毁阶段,deactivate 和 dispose。
组件首次加载执行过程
首先我们来实现下面这段代码(类似于 flutter 自己的计数器项目),康康组件首次创建是否按照上述流程图中的顺序来执行的。
1.创建一个 flutter 项目;2.创建 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 的一些生命周期都进行了重写,并且在执行中都打印了标识,方便看到函数的执行顺序。
1.在 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是可以接收到这些通知,但有个别情况下是无法接收到通知的,比如用户关机等。它的四种生命周期状态枚举源码中有详细的介绍和说明,下面附上源码以及简单的翻译说明。
除了 app 生命周期的方法,Flutter 还有一些其他不属于生命周期,但是也会在一些特殊时机被观察到的方法,如 didChangeAccessibilityFeatures(当前系统改变了一些访问性活动的回调)
、didHaveMemoryPressure(低内存回调)
、didChangeLocales(用户本地设置变化时调用,如系统语言改变)
、didChangeTextScaleFactor(文字系数变化)
等,如果有兴趣的话,可以去试一试。
总结
本篇文章主要介绍了 Widget 中的 StatefulWidget 的生命周期,以及 Flutter App 相关的生命周期。但是要切记,StatefulWidget 虽好,但也不要无脑的所有 Widget 全都用它,能使用 StatelessWidget 还是要尽量去使用 StatelessWidget(仔细想一下,这是为什么呢?)。好啦,看完本篇文章,你就是 Flutter 初级开发工程师了,可以去面试了(狗头保命)。
GDG广州创立于2009年,是全球GDG社区中最活跃和知名的技术社区之一,每年举办数十场大大小小的科技活动,每年影响十几万以广深莞为中心,辐射粤港澳大湾区的开发者及科技从业人员。
有意愿成为GDG社区志愿者、赞助商、讲师及社区合作伙伴的朋友,请联系wechatID:lyyahyc
社区成员加入方式:
2.社区成员可以通过邮箱接收到我们的活动信息,请发任意邮件至以下邮箱,发送空邮件到[email protected]即完成订阅