【老孟Flutter】Stateful 组件的生命周期

老孟导读:关于生命周期的文章共有2篇,第一篇是介绍 Flutter 中Stateful 组件的生命周期。
博客地址:http://laomengit.com/blog/20201227/Stateful%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html

第二篇是 Flutter 中与平台相关的生命周期,

博客地址:http://laomengit.com/blog/20201227/%E7%9B%B8%E5%85%B3%E5%B9%B3%E5%8F%B0%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html

博客中还有更多精彩文章,也欢迎加入 Flutter 交流群。

此篇文章介绍 StatefulWidget 组件的生命周期, StatefulWidget 组件的生命周期时非常重要的知识点,就像 Android 中 Activity 的生命周期一样,不仅在以后的工作中经常用到,面试也会经常被问到。

在 Flutter 中一切皆 组件,而组件又分为 StatefulWidget(有状态)StatelessWidget(无状态)组件 ,他们之间的区别是 StatelessWidget 组件发生变化时必须重新创建新的实例,而 StatefulWidget 组件则可以直接改变当前组件的状态而无需重新创建新的实例。

注意:使用的 Flutter 版本 和 Dart 版本如下:

Flutter 1.22.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 1aafb3a8b9 (6 weeks ago) • 2020-11-13 09:59:28 -0800
Engine • revision 2c956a31c0
Tools • Dart 2.10.4

不同的版本 StatefulWidget 组件的生命周期会有差异。

下面的 StatefulWidget 和 State 结构图是StatefulWidget 组件生命周期的概览,不同版本的差异也可以对比此结构图。

生命周期流程图:

下面详细介绍 StatefulWidget 组件的生命周期。

生命周期一:createState

下面是一个非常简单的 StatefulWidget 组件:

class StatefulWidgetDemo extends StatefulWidget {
  @override
  _StatefulWidgetDemoState createState() => _StatefulWidgetDemoState();
}

class _StatefulWidgetDemoState extends State {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

当我们构建一个 StatefulWidget 组件时,首先执行其构造函数(上面的代码没有显示的构造函数,但有默认的无参构造函数),然后执行 createState 函数。但构造函数并不是生命周期的一部分。

当 StatefulWidget 组件插入到组件树中时 createState 函数由 Framework 调用,此函数在树中给定的位置为此组件创建 State,如果在组件树的不同位置都插入了此组件,即创建了多个此组件,如下:

Row(children: [
  MyStatefulWidget(),
  MyStatefulWidget(),
  MyStatefulWidget(),
],)

那么系统会为每一个组件创建一个单独的 State,当组件从组件树中移除,然后重新插入到组件树中时, createState 函数将会被调用创建一个新的 State

createState 函数执行完毕后表示当前组件已经在组件树中,此时有一个非常重要的属性 mountedFramework 设置为 true

生命周期二:initState

initState 函数在组件被插入树中时被 Framework 调用(在 createState 之后),此函数只会被调用一次,子类通常会重写此方法,在其中进行初始化操作,比如加载网络数据,重写此方法时一定要调用 super.initState(),如下:

@override
void initState() {
  super.initState();
  //初始化...
}

如果此组件需要订阅通知,比如 ChangeNotifier 或者 Stream,则需要在不同的生命周期内正确处理订阅和取消订阅通知。

  • initState 中订阅通知。
  • didUpdateWidget 中,如果需要替换旧组件,则在旧对象中取消订阅,并在新对象中订阅通知。
  • 并在 dispose 中取消订阅。

另外在此函数中不能调用 BuildContext.dependOnInheritedWidgetOfExactType,典型的错误写法如下:

@override
void initState() {
  super.initState();
  IconTheme iconTheme = context.dependOnInheritedWidgetOfExactType();
}

异常信息如下:

解决方案:

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  context.dependOnInheritedWidgetOfExactType();
}

上面的用法作为初学者使用的比较少,但下面的错误代码大部分应该都写过:

@override
void initState() {
  super.initState();
  showDialog(context: context,builder: (context){
    return AlertDialog();
  });
}

异常信息如下:

解决方案:

@override
void initState() {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
    showDialog(context: context,builder: (context){
      return AlertDialog(title: Text('AlertDialog'),);
    });
  });
}

注意:弹出 AlertDialog 在 didChangeDependencies 中调用也会出现异常,但和上面的异常不是同一个。

生命周期三:didChangeDependencies

didChangeDependencies 方法在 initState 之后由 Framework 立即调用。另外,当此 State 对象的依赖项更改时被调用,比如其所依赖的 InheritedWidget 发生变化时, Framework 会调用此方法通知组件发生变化。

此方法是生命周期中第一个可以使用 BuildContext.dependOnInheritedWidgetOfExactType 的方法,此方法很少会被重写,因为 Framework 会在依赖发生变化时调用 build,需要重写此方法的场景是:依赖发生变化时需要做一些耗时任务,比如网络请求数据。

didChangeDependencies 方法调用后,组件的状态变为 dirty,立即调用 build 方法。

生命周期四:build

此方法是我们最熟悉的,在方法中创建各种组件,绘制到屏幕上。 Framework会在多种情况下调用此方法:

  • 调用 initState 方法后。
  • 调用 didUpdateWidget 方法后。
  • 收到对 setState 的调用后。
  • State 对象的依存关系发生更改后(例如,依赖的 InheritedWidget 发生了更改)。
  • 调用 deactivate 之后,然后将 State 对象重新插入树的另一个位置。

此方法可以在每一帧中调用,此方法中应该只包含构建组件的代码,不应该包含其他额外的功能,尤其是耗时任务。

生命周期五:didUpdateWidget

当组件的 configuration 发生变化时调用此函数,当父组件使用相同的 runtimeTypeWidget.key 重新构建一个新的组件时,Framework 将更新此 State 对象的组件属性以引用新的组件,然后使用先前的组件作为参数调用此方法。

@override
void didUpdateWidget(covariant StatefulLifecycle oldWidget) {
  super.didUpdateWidget(oldWidget);
  print('didUpdateWidget');
}

此方法中通常会用当前组件与前组件进行对比。Framework 调用完此方法后,会将组件设置为 dirty 状态,然后调用 build 方法,因此无需在此方法中调用 setState 方法。

生命周期六:deactivate

当框架从树中移除此 State 对象时将会调用此方法,在某些情况下,框架将重新插入 State 对象到树的其他位置(例如,如果包含该树的子树 State 对象从树中的一个位置移植到另一位置),框架将会调用 build 方法来提供 State 对象适应其在树中的新位置。

生命周期七:dispose

当框架从树中永久移除此 State 对象时将会调用此方法,与 deactivate 的区别是,deactivate 还可以重新插入到树中,而 dispose 表示此 State 对象永远不会在 build。调用完 dispose后,mounted 属性被设置为 false,也代表组件生命周期的结束,此时再调用 setState 方法将会抛出异常。

子类重写此方法,释放相关资源,比如动画等。

非常重要的几个概念

下面介绍几个非常重要的概念和方法,这些并不是生命周期的一部分,但是生命周期过程中的产物,与生命周期关系非常紧密。

mounted

mounted 是 State 对象中的一个属性,此属性表示当前组件是否在树中,在创建 State 之后,调用 initState 之前,Framework 会将 StateBuildContext 进行关联,当 Framework 调用 dispose 时,mounted 被设置为 false,表示当前组件已经不在树中。

createState 函数执行完毕后表示当前组件已经在组件树中,属性 mountedFramework 设置为 true,平时写代码时或者看其他开源代码时经常看到如下代码:

if(mounted){
  setState(() {
    ...
  });
}

强烈建议:在调用 setState 时加上 mounted 判断。

为什么要加上如此判断?因为如果当前组件未插入到树中或者已经从树中移除时,调用 setState 会抛出异常,加上 mounted 判断,则表示当前组件在树中。

dirty 和 clean

dirty 表示组件当前的状态为 脏状态,下一帧时将会执行 build 函数,调用 setState 方法或者 执行 didUpdateWidget 方法后,组件的状态为 dirty

cleandirty 相对应,clean 表示组件当前的状态为 干净状态clean 状态下组件不会执行 build 函数。

setState

setState 方法是开发者经常调用的方法,此方法调用后,组件的状态变为 dirty,当有数据要更新时,调用此方法。

reassemble

reassemble 用于开发,比如 hot reload ,在 release 版本中不会回调此方法。

交流

老孟Flutter博客(330个控件用法+实战入门系列文章):http://laomengit.com

你可能感兴趣的:(【老孟Flutter】Stateful 组件的生命周期)