欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Flutter - 生命周期

程序员文章站 2023-01-22 15:33:52
StatefulWidget 需要借助于 State 对象,在特定的阶段来处理用户的交互或其内部数据的变化,并体现在 UI 上。这个特定的阶段,就涵盖来一个组件从加载到卸载的全过程,即生命周期。Flutter 中的 Widget 也存在生命周期,并且通过 State 来体现。 而 App 则是一个特 ......

statefulwidget 需要借助于 state 对象,在特定的阶段来处理用户的交互或其内部数据的变化,并体现在 ui 上。这个特定的阶段,就涵盖来一个组件从加载到卸载的全过程,即生命周期。flutter 中的 widget 也存在生命周期,并且通过 state 来体现。

而 app 则是一个特殊的 widget。除了需要处理视图显示的各个阶段(即视图的生命周期)之外,还需要应对应用从启动到退出所经历的各个状态(app 的生命周期)。

state 生命周期

state 的生命周期,指的是在用户参与的情况下,其关联的 widget 所经历的,从创建到显示再到更新最后到停止,直至销毁等各个过程阶段。state 的生命周期可以分为 3 个阶段:创建(插入视图树)、更新(在视图树中存在)、销毁(从视图树中移除)。

创建

state 初始化时会依次执行:构造方法 -> initstate -> dischangedependencies -> build,随后完成页面渲染。

初始化过程中每个方法的意义如下:

  • 构造方法是 state 生命周期的起点,flutter 会通过调用 statefulwidget.createstate() 来创建一个 state。可以通过构造方法,来接收父 widget 传递的初始化 ui 配置数据。这些配置数据,决定了 widget 最初的呈现效果。
  • initstate,会在 state 对象被插入视图树的时候调用。这个函数在 state 的生命周期中只会被调用一次,所以可以在这里做一些初始化工作,比如为状态变量设定默认值。
  • didchangedependencies 则来专门处理 state 对象依赖关系变化,会在 initstate() 调用结束后,被 flutter 调用。
  • build,作用是构建视图。经过以上步骤,framework 认为 state 已经准备好了,于是调用 build。可以在这个函数中,根据父 widget 传递过来的初始化配置数据,以及 state 的当前状态,创建一个 widget 然后返回。

更新

widget 的状态更新,主要由 3 个方法触发:setstate、didchangedependencies 与 didupdatewidget.

如上三个方法对应的调用场景:

  • setstate:当状态数据发生变化时,通过调用这个方法告诉 flutter:“这儿的数据变了,请使用更新后的数据重建 ui!”。
  • didchangedependencies:state 对象的依赖关系发生变化后,flutter 会回调这个方法,随后触发组建构建。典型的场景是,系统语言 locale 或应用主题改变时,系统会通知 state 执行 didchangedependencies 回调方法。
  • didupdatewidget:当 widget 的配置发生变化时,比如,父 widget 触发重建(即父 widget 的状态发生变化时),热重载时,系统会调用这个函数
一旦这三个方法被调用,flutter 随后就会销毁老 widget,并调用 build 方法重建 widget。

销毁

组件销毁比较简单。比如组件被移除,或是页面销毁的时候,系统会调用 deactivate 和 dispose 这两个方法,来移除或销毁组件。

具体调用机制如下:

  • 当组件的可见状态发生变化时,deactivate 函数会被调用,这时 state 会被暂时从视图树中移除。值得注意的是,页面切换时,由于 state 对象在视图树中的位置发生了变化,需要先暂时移除后再重新添加,重新触发组件构建,因此这个函数也会被调用。
  • 当 sate 被永久地从视图树中移除时,flutter 会调用 dispose 函数。而一旦到这个阶段,组件就要被销毁了,所以我们可以在这里进行最终的资源释放、移除监听、清理环境,等等。

从功能、调用时机和调用次数的纬度总结这些方法如下;

方法名 功能 调用时机 调用次数
构造方法 接收父widget传递的初始化ui配置数据 创建state时 1
initstate 与渲染相关的初始化工作 在state被插入视图树时 1
didchangedependencies 处理state队形依赖关系变化 initstate后及state对象依赖关系变化时 >= 1
build 构建视图 state准备好数据需要渲染时 >= 1
setstate 触发视图重建 需要刷新ui时 >= 1
didupdatewidget 处理widget的配置变化 父widget setstate触发子widget重建时 >= 1
deactivate 组件被移除 组件不可视 >= 1
dispose 组件被销毁 组件被永久移除 1

app 生命周期

视图的生命周期,定义了视图的加载到构建的全过程,其回调机制能够确保我们可以根据视图的状态选择合适的时机做恰当的事情。app 的生命周期,则定义了 app 从启动到退出的全过程。

在原生 android、ios 开发中,有时需要在对应的 app 生命周期事件中做相应处理,比如 app 从后台进入前台、从前台退到后台,或是在 ui 绘制完成后做一些处理。

在原生开发中,可以通过重写 activity、viewcontroller 生命周期回调方法,或是注册应用程序的相关通知,来监听 app 的生命周期并做相应的处理。而在 flutter 中,我们可以利用 widgetsbindingobserver 类,来实现同样的需求。

widgetsbindingobserver 中具体回调函数有如下一些方法:

abstract class widgetsbindingobserver {
  // 页面 pop
  future<bool> didpoproute() => future<bool>.value(false);
  // 页面 push
  future<bool> didpushroute(string route) => future<bool>.value(false);
  // 系统窗口相关改变回调,如旋转
  void didchangemetrics() { }
  // 文本缩放系数变化
  void didchangetextscalefactor() { }
  // 系统亮度变化
  void didchangeplatformbrightness() { }
  // 本地化语言变化
  void didchangelocales(list<locale> locale) { }
  //app 生命周期变化
  void didchangeapplifecyclestate(applifecyclestate state) { }
  // 内存警告回调
  void didhavememorypressure() { }
  //accessibility 相关特性回调
  void didchangeaccessibilityfeatures() {}
}
其他回调相对简单,可以查看官方文档

生命周期回调

didchangeapplifecyclestate 回调函数中,有一个参数类型为 applifecyclestate 的枚举类,这个枚举类是 flutter 对 app 生命周期状态的封装。它的常用状态包括 resumed、inactive、paused 这三个。

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

案例分享:在 initstate 时注册来监听器,在 didchangeapplifecyclestate 回调方法中打印来当前的 app 状态,最后在 dispose 时把监听器移除:

class _myhomepagestate extends state<myhomepage> with widgetsbindingobserver {
  ... 
  @override
  @mustcallsuper
  void initstate() {
    super.initstate();
    widgetsbinding.instance.addobserver(this); // 注册监听器
  }
  
  @override
  @mustcallsuper
  void dispose() {
    super.dispose();
    widgetsbinding.instance.removeobserver(this); // 移除监听器
  }
  
  @override
  void didchangeapplifecyclestate(applifecyclestate state) async {
    print("$state");
    if (state == applifecyclestate.resumed) {
      // do sth
    }
  }
}

尝试这切换一下前、后台,观察控制台输出的 app 状态,可以发现:

  • 从后台切入前台,控制台打印的 app 生命周期变化如下:applifecyclestate.paused -> applifecyclestate.inactive -> applifecyclestate.resumed;
  • 从前台退回后台,控制台打印的 app 生命周期变化则变成了:applifecyclestate.resumed -> applifecyclestate.inactive -> applifecyclestate.paused

帧绘制回调

除了需要监听 app 的生命周期回调做相应的处理之外,有时候还需要在组件渲染之后做一些与显示安全相关的操作。

在 ios 开发中,可以通过 dispatch_async(dispatch_get_main_queue(),^{...}) 方法,让操作在下一个 runloop 执行;在 android 开发中,可以通过 view.post() 插入消息队列,来保证在组件渲染后进行相关操作。

在 flutter 中实现同样的需求会更简单: 依然使用万能的 widgetsbinding 来实现。

widgetsbinding 提供了单次 frame 绘制回调,以及实时 frame 绘制回调两种机制,来分别满足不同的需求:

  • 单次 frame 绘制回调,通过 addpostframecallback 实现。它会在当前 frame 绘制完成后进行回调,并只会回调一次,如果要再次监听则需要再设置一次。
widgetsbinding.instance.addpostframecallback((_){
      print(" 单次 frame 绘制回调"); // 只回调一次
});
  • 实时 frame 绘制回调,则通过 addpersistentframecallback 实现。这个函数会在每次绘制 frame 结束后进行回调,可以用作 fps 检测。
widgetsbinding.instance.addpersistentframecallback((_){
      print(" 实时 frame 绘制回调"); //  每帧都回调
});