Flutter 生命周期(原生APP生命周期对比)

381 阅读9分钟

作为一名前端开发工程师,刚接触 Flutter 的时候,一定会有这样的疑问:Flutter 的生命周期是怎么样的?是如何处理生命周期的?我的 [Android] 在哪里?[iOS] 呢? 我的业务逻辑应该放在哪里处理?初始化数据呢?网络加载了?

iOS

84f235984a714297b093756aea52e6ef~tplv-k3u1fbpfcp-zoom-in-crop-mark-3024-0-0-0.image.png

load 方法

  • load方法是在main函数之前执行的也就是在运行时候也就是程序的类文件被装载的时候调用的。 通常我们会在这里用Runtime的一些API 来控制我们的代码。值得注意的是 load的方法执行顺序大概是 父类>>子类>> Category。 并且如果子类没有实现load方法的话父类的load方法也不会被调用。像路由注册,RunTime黑魔法,无痕埋点都是这个方法进行处理。

initialize方法

  • initialize方法是在第一次调用该类的方法的时候调用,但是这个方法有一个特点,就是就算子类没有实现initialize方法 父类的initialize方法也会被调用。这个方法通常用作优化APP的一些代码。

Flutter

知道了iOS 的生命周期,那么 Flutter 呢?有和移动端对应的生命周期函数么?之前我们都或多或少都学习了Flutter,你会发现 Flutter 中有两个主要的 Widget:StatelessWidget(无状态)StatefulWidget(有状态) 。下面来介绍下 StatefulWidget,因为它有着原生相似的生命周期。

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,也代表组件生命周期的结束。

flutter 生命周期流程图

533b6573ae124afca6104529b799f5e3~tplv-k3u1fbpfcp-zoom-in-crop-mark-3024-0-0-0.image.png

大致分为四个阶段

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

组件首次加载执行过程

首先我们来实现下面这段代码(类似于 flutter 自己的计数器项目),康康组件首次创建是否按照上述流程图中的顺序来执行的。

  1. 创建一个 flutter 项目;
  2. 创建 second_state.dart 中添加计数原始下代码,
  3. 在 main.dart 中加载该组件 生命周期 进行全部重写,并打印标识,能看到函数的执行顺序。

这时 CountWidget 作为 MyHomePage 的子组件。我们打开模拟器,开始运行。在控制台可以看到如下日志,可以看出 StatefulWidget 在第一次被创建的时候是调用下面四个函数。

flutter: count createState
flutter: count initState
flutter: count didChangeDependencies
flutter: count build
复制代码

点击屏幕上的 ➕ 按钮, _count 增加 1,模拟器上的数字由 0 变为 1,日志如下。也就是说在状态发生变化的时候,会调用 setStatebuild 两个函数。

flutter: count setState
flutter: count build
复制代码

command + s 热重载后,日志如下:

flutter: count reassemble
flutter: count didUpdateWidget
flutter: count build
复制代码

注释掉 main.dart 中的 CountWidget,command + s 热重载后,这时 CountWidget 消失在模拟器上,日志如下:

flutter: count reassemble
flutter: count deactivate
flutter: count dispose
复制代码

经过上述一系列操作之后,通过日志打印并结合生命周期流程图,我们可以很清晰的看出各生命周期函数的作用以及理解生命周期的几个阶段。 我们已经发现了一个细节,那就是 build 方法在不同的操作中都被调用了,下面我们来介绍什么情况下会触发组件再次 build。

触发组件再次 build

触发组件再次 build 的方式有三种,分别是 setStatedidChangeDependenciesdidUpdateWidget

1.setState 很好理解,只要组件状态发生变化时,就会触发组件 build。在上述的操作过程中,点击 ➕ 按钮,_count 会加 1,

2.didChangeDependencies,组件依赖的全局 state 发生了变化时,也会调用 build。例如系统语言等、主题色等。

3.didUpdateWidget,我们以代码为例。在 main.dart 中,同样的重写生命周期函数,并打印。在 CountWidget 外包一层 Column ,并创建同级的 RaisedButton 做为父 Widget 中的计数器。

重新加载 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
复制代码

可以发现:

  • 父组件也经历了 createStateinitStatedidChangeDependenciesbuild 这四个过程。
  • 并且父组件要在 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
复制代码

因为是热重载,所以会调用 reassembledidUpdateWidgetbuild,我们可以忽略带有这几个函数的打印日志。可以得出结论: 父组件移除,会先移除节点,然后子组件移除节点,子组件被永久移除,最后是父组件被永久移除。

Flutter App Lifecycle

上面我们介绍的生命周期主要是 StatefulWidget 组件的生命周期,下面我们来简单介绍一下和 app 平台相关的生命周期,比如退出到后台。

我们创建 app_lifecycle_state.dart 文件并创建 AppLifecycle,他是一个 StatefulWidget,但是他要继承 WidgetsBindingObserver。

didChangeAppLifecycleState 方法是重点,AppLifecycleState 中的状态包括:resumedinactivepauseddetached 四种。

didChangeAppLifecycleState 方法的依赖于系统的通知(notifications),正常情况下,App是可以接收到这些通知,但有个别情况下是无法接收到通知的,比如用户关机等。它的四种生命周期状态枚举源码中有详细的介绍和说明,下面附上源码以及简单的翻译说明。

app_life_cycle_state

  • resumed:该应用程序是可见的,并对用户的输入作出反应。也就是应用程序进入前台。

  • inactive:应用程序处于非活动状态,没有接收用户的输入。在 iOS 上,这种状态对应的是应用程序或 Flutter 主机视图在前台非活动状态下运行。当处于电话呼叫、响应 TouchID 请求、进入应用切换器或控制中心时,或者当 UIViewController 托管的 Flutter 应用程序正在过渡。在 Android 上,这相当于应用程序或 Flutter 主机视图在前台非活动状态下运行。当另一个活动被关注时,如分屏应用、电话呼叫、画中画应用、系统对话框或其他窗口,应用会过渡到这种状态。也就是应用进入后台。

  • pause:该应用程序目前对用户不可见,对用户的输入没有反应,并且在后台运行。当应用程序处于这种状态时,引擎将不会调用。也就是说应用进入非活动状态。

  • detached:应用程序仍然被托管在flutter引擎上,但与任何主机视图分离。处于此状态的时机:引擎首次加载到附加到一个平台 View 的过程中,或者由于执行 Navigator pop,view 被销毁。