背景

作为 Android 开发者,我们对 Activity、Service 等系统组件的生命周期非常熟悉,在日常开发中也会利用组件的生命周期特点开发业务逻辑。

Flutter 中有“一切皆Widget”的说法,Widget 是 Flutter 功能的抽象描述,是视图的配置信息,也是数据的映射。Android 开发中的 View、Layout、Activity、Application等,对应到 Flutter 中都是 Widget。

Flutter 的 Widget 有 StatelessWidget 和 StatefulWidget 两种类型,StatelessWidget 用于处理静态的、无状态变化的视图展示,而 StatefulWidget 应对有交互、需要动态变化视觉效果的场景。

StatelessWidget 通过父 Widget 初始化时传入的静态配置就能完全控制其静态展示,而 StatefulWidget 需要借助于 State 对象,在特定阶段来处理用户交互或内部数据的变化,然后更新到 UI 上。这些特定的阶段涵盖了一个 Widget 从初始化到卸载的全过程,即生命周期。Flutter 中的生命周期即指的 StatefulWidget 的生命周期,并通过 State 对象来体现,StatelessWidget 的生命周期只有 build 过程,且只会执行一次。

生命周期方法

Flutter中的生命周期方法主要包括以下内容:

  • createState
    该函数为 StatefulWidget 中创建 State 的方法,当 StatefulWidget 被调用时会立即执行 createState 。
class MyPage extends StatefulWidget {
  @override
  _MyPageState createState() => _MyScreenState();
}
  • initState
    该函数为 State 初始化调用,因此可以在此期间执行 State 各变量的初始赋值,同时也可以在此期间与服务端交互,获取服务端数据后调用 setState 来设置 State。
int a;
@override
void initState() {
  a = 0;
  super.initState();
}
  • didChangeDependencies
    用来专门处理 State 对象依赖关系的变化,这里说的 State 为全局 State ,例如语言或者主题等,会在 initState() 方法调用结束后被调用。
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
  • build
    主要是构建视图,返回需要渲染的 Widget ,由于 build 会被调用多次,因此在该函数中只能做返回 Widget 相关逻辑,避免因为执行多次导致状态异常。
@override
Widget build(BuildContext context, MyButtonState state) {
  return Container(color:Colors.red);
}
  • reassemble
    主要是提供开发阶段使用,在 debug 模式下,每次热重载都会调用该函数,因此在 debug 阶段可以在此期间增加一些 debug 代码,来检查代码问题。

  • didUpdateWidget
    该函数主要是在组件重新构建,比如说热重载,父组件发生 build 的情况下,子组件该方法才会被调用,其次该方法调用之后一定会再调用本组件中的 build 方法。

@override
void didUpdateWidget(MyHomePage oldWidget) {
  super.didUpdateWidget(oldWidget)
}
  • deactivate
    在组件被移除节点后会被调用,如果该组件被移除节点,然后未被插入到其他节点时,则会继续调用 dispose 永久移除。
@override
void deactivate() {
  super.deactivate();
}
  • dispose
    永久移除组件,并释放组件资源。
@override
dispose() {
  super.dispose();
}

Widget生命周期流程

Widget 的生命周期主要体现在 State 对象的回调方法中,主要执行流程如下图:

上述过程可以分为3个主要阶段:创建 Widget、更新 Widget 和销毁 Widget。

创建

State 的初始化流程是从系统调用 StatefulWidget 的 createWidget方 法开始的,在 createWidget 方法中会调用 State 类的构造方法,接着依次执行State类生命周期方法:initState -> didChangeDependencies -> build,然后完成页面渲染。

构造方法是 State 生命周期的起点,Flutter 通过调用 StatefulWidget.createState() 创建一个 State,父 Widget 可以通过构造方法传入一些初始化 UI 的数据。当 State 对象被插入到视图树时会调用 initState 方法,这个方法在 State 对象的生命周期中只会被调用一次,我们可以在这里做一些初始化工作。didChangeDependencies 方法是用来专门处理 State 对象依赖关系变化的,它会在 initState 执行结束后立即被调用。build 方法用于构建UI视图,经过前面的步骤,当前 State 对象已经准备好了,在 build 方法中根据父 Widget 传递的初始化数据以及 State 的当前状态,创建一个 Widget 返回。

更新

当一个Widget收到其父 Widget 的重构请求,或者由于用户的交互触发内部 State 对象改变并调用 setState 方法,或者当前 Widget 被插入到新的视图树中,或者在开发过程中发生热重载,将触发 Widget 的 State 更新,更新的场景比较复杂,每次更新都会调用 build 方法。State 更新主要由3个方法触发:setState、didchangeDependencies 与 didUpdateWidget。

父 Widget 根据 runtimeType 和 Widget.key 确定需要更新的视图树中某个位置的 Widget,当 Widget 的配置发生变化或热重载时,会调用 didUpdateWidget 方法。当 State 内部状态改变时,可以在当前 State 中直接调用 setState 方法更新状态数据,setState 的回调是立即执行的,所以不能是 async 的。当 State 对象的依赖关系发生变化,比如系统语言或应用主题等全局的状态发生改变后,Framework 会通知 State 执行 didChangeDependencies 方法。

销毁

State 对象的销毁过程比较简单,包括移除和销毁 Widget 两个场景,系统会分别调用 deactivate 和 dispose 方法。

当组件的可见状态发生变化时,deactivate 函数会被调用,这时 State 会被暂时从视图树中移除。值得注意的是,页面切换时,由于 State 对象在视图树中的位置发生了变化,需要先暂时移除后再重新添加,重新触发组件构建,因此这个函数也会被调用。

当 State 被永久地从视图树中移除时,Flutter 会调用 dispose 函数。而一旦到这个阶段,组件就要被销毁了,所以我们可以在这里进行最终的资源释放、移除监听、清理环境等等。

App生命周期

State 的生命周期定义了视图从加载到构建以及更新和销毁的全过程,根据其回调机制,我们可以在合适的回调方法中根据 Widget 的状态做相应的逻辑响应。在原生 Android 开发中,我们经常会处理 App 从后台进入前台、从前台切到后台,或者是 UI 绘制完成等时机的一些状态逻辑,通过重写 Activity 的生命周期回调方法或注册应用程序的相关通知,监听 App 的生命周期并做相应的处理。在 Flutter 中我们可以监听 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() {}
}

可以看出 WidgetsBindingObserver 类提供了很多回调方法,比如屏幕旋转、屏幕亮度变化、语言变化、内存警告等都可以通过监听这个类实现回调。通过给 WidgetsBinding 的单例对象设置监听器,就可以监听对应的回调方法。

生命周期回调

didChangeAppLifecycleState 回调函数中,有一个参数类型为 AppLifecycleState 的枚举类,这个枚举类是 Flutter 对 App 生命周期状态的封装。它包括 resumed、inactive、paused 和 detached 4个状态,常用的是前3个。

  • resumed:App可见,并能响应用户的输入
  • inactive:App处于不活动状态,无法处理用户响应;此时App可能仍可见,但在Android中被其他的Activity或Window抢占了焦点,在iOS中类似被其他页面获取了焦点
  • paused:App不可见并不能响应用户的输入,但是在后台继续活动中

当切换Flutter应用到前后台时,App的状态变化如下:

  1. 从前台退回后台:AppLifecycleState.resumed -> AppLifecycleState.inactive -> AppLifecycleState.paused
  2. 从后台切入前台:AppLifecycleState.paused -> AppLifecycleState.inactive -> AppLifecycleState.resumed

帧绘制回调

除了需要监听 App 的生命周期回调做相应的处理之外,有时候我们还需要在组件渲染之后做一些与显示安全相关的操作。在Flutter中同样使用WidgetsBinding来监听帧绘制回调,提供了单次 Frame 绘制回调,以及实时 Frame 绘制回调两种机制:

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

页面切换监听

Flutter提供了可以监听App中页面间切换的接口类RouteAware,用以在合适的时候对页面中控件进行动画暂停或者资源释放。

/// An interface for objects that are aware of their current [Route].
///
/// This is used with [RouteObserver] to make a widget aware of changes to the
/// [Navigator]'s session history.
abstract class RouteAware {
  /// Called when the top route has been popped off, and the current route
  /// shows up.
  void didPopNext() { }

  /// Called when the current route has been pushed.
  void didPush() { }

  /// Called when the current route has been popped off.
  void didPop() { }

  /// Called when a new route has been pushed, and the current route is no
  /// longer visible.
  void didPushNext() { }
}

假如有3个页面分别是A、B、C,跳转逻辑由A->B->C,而RouteAware使用with混淆在B中。

  • didPopNext:在C页面关闭后,B页面调起该方法;
  • didPush: 当由A打开B页面时,B页面调起该方法;
  • didPop: 当B页面关闭时,B页面调起该方法;
  • didPushNext: 当从B页面打开C页面时,该方法被调起。

使用方法

  1. 使用 with 关键字在 State 类中使用,使用前在 MaterialApp 中定义一个 RouteObserver 对象
final RouteObserver<Route<dynamic>> routeObserver = RouteObserver();

class MyApp extends StatelessWidget {
    Widget _buildMaterialApp() => MaterialApp(
      initialRoute: '/',
      navigatorObservers: [routeObserver], //添加路由观察者
      onGenerateRoute: _onGenerateRoute);
}
  1. 在B页面中混淆 RouteAware,并注册 RouteObserver,记得在 dispose 方法中取消注册
class B extends StatefulWidget {
     B({Key key}) : super(key: key);
}

class BState extends State<B>  with RouteAware {

  @override
  void didChangeDependencies() {
    routeObserver.subscribe(this, ModalRoute.of(context)); //订阅
    super.didChangeDependencies();
  }

  @override
  void didPush() {
    debugPrint("didPush");
    super.didPush();
  }

  @override
  void didPop() {
    debugPrint("didPop");
    super.didPop();
  }

  @override
  void didPopNext() {
    debugPrint("didPopNext");
    super.didPopNext();
  }
  
@override
  void didPushNext() {
    debugPrint("didPushNext");
    super.didPushNext();
  }

  @override
  void dispose() {
    routeObserver.unsubscribe(this); //取消订阅
    super.dispose();
  }
}

总结

  1. Flutter Widget 生命周期的实际承载者是State类,其主要过程分为创建(插入视图树)、更新(在视图树中发生状态变化)和销毁(从视图树中移除)3个阶段;
  2. StatelessWidget 用于不需要维护状态的场景,只会执行一次 build 方法,StatefulWidget 的 build 方法会被多次执行,触发函数是 setState、didChangeDependencies、didUpdateWidget;
  3. Flutter App 生命周期可以通过 WidgetsBinding 注册监听 WidgetsBindingObserver 类的回调方法来获取;
  4. 帧绘制回调可以通过 WidgetsBinding 注册 FrameCallback 的回调方法获取,包括单次 Frame 绘制回调和实时 Frame 绘制回调;
  5. 监听页面间切换使用 RouteAware。

Refers To

State class
Flutter widget生命周期详解
Flutter - 监视页面的切换