Flutter 的生命周期

概述

生命周期是一个 widget 组件加载到卸载的整个周期,熟悉生命周期可以让我们在合适的时机做该做的事情。

Flutter 开发中,everything is widget,但我们一般都不用直接继承 Widget 类来实现一个新组件,我们通常会通过继承 StatelessWidgetStatefulWidget 来间接继承 Widget 类来实现。StatelessWidgetStatefulWidget 都是直接继承自 Widget 类,而这两个类也正是 Flutter 中非常重要的两个抽象类,它们引入了两种 Widget 模型。此文主要介绍这两种 widget 的生命周期。

StatelessWidget

StatelessWidget 是无状态的 Widget,一旦创建就不会发生变化,所以无法提供 setState 修改组件的状态,它内部属性应声明为 final,防止意外发生改变。所以 StatelessWidget 的生命周期只有一个,就是 buildbuild 是用来创建 Widget 的,但因为 build 在每次界面刷新的时候都会调用,所以不要在 build 里写业务逻辑,可以把业务逻辑写到你的 StatelessWidget 的构造函数里。其生命周期如下图:

StatefulWidget

StatefulWidget 是有状态的 Widget,它的 state 在发生变化时会重新渲染 UI,提供 setState 方法修改组件的状态,它的生命周期主要在 State 这块,其生命周期如下图:

下面解释下各个函数:

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

  • didChangeDependencies()State 对象的依赖发生变化时会被调用;比如其所依赖的 InheritedWidget 发生变化时, Framework 会调用此方法通知组件发生变化。典型的场景是当系统语言 Locale 或应用主题改变时,Flutter framework 会通知 widget 调用此回调。

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

  • build用于构建 Widget 的,会在如下场景被调用:

    1. 在调用 initState() 之后。
    2. 在调用 didUpdateWidget() 之后。
    3. 在调用 setState() 之后。
    4. 在调用 didChangeDependencies() 之后。
    5. State 对象从树中一个位置移除后(会调用 deactivate )又重新插入到树的其它位置之后。

注意:此方法中应该只包含构建组件的代码,不应该包含其他额外的功能,尤其是耗时任务。

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

  • didUpdateWidget()在父 widget 重新构建子  widget  时,子 widgetdidUpdateWidget 可能会被调用。 之所以说可能,是因为在父 widget 重新构建时,Flutter Framework 会调用 Widget.canUpdate 来检测Widget 树中同一位置的新旧节点的 keyruntimeType 是否同时相等,新旧 widgetkeyruntimeType 同时相等时会返回 true,此时 didUpdateWidget() 就会被调用,如果新旧节点的 keyruntimeType 没有同时相等,didUpdateWidget() 不会被调用。

注意:Framework 调用完此方法后,会将组件设置为 dirty 状态,然后调用 build 方法。

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

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

实例展示

展示随机数的一个 demo,点击随机按钮,更新随机数,打印此 demo 的生命周期函数的调用流程,代码如下:

点击run首次启动,打印log如下:

点击切换随机数按钮,打印 log 如下:

点击切换随机数按钮,会调用 setState()setState 会调用 build 重新构建 widget

点击 hot reload 按钮,打印 log 如下:

App生命周期

通过 WidgetsBindingObserverdidChangeAppLifecycleState 可以获取 App 的生命周期状态。生命周期在 AppLifecycleState 类中。常用状态包含如下几个:

  • resumed:处于可见并能响应用户的输入
  • inactive:处于不活跃状态且无法处理用户的响应
  • paused:处于不可见且不能响应用户的输入,但是在后台继续活跃

举例如下:

记得注册和移除监听。

生命周期相关的一些注意点

  • 页面渲染完毕回调 addPostFrameCallbackStatefulWidget 渲染结束的回调,只会被调用一次,之后 StatefulWidget 刷新 UI 也不会被调用。在 initState 不能调用BuildContext.dependOnInheritedWidgetOfExactType,可以使用 addPostFrameCallback 规避此限制:

  • mounted

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

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

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

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

  • dirtyclean

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

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

参考链接


Flutter 的生命周期