面试官:知道 Flutter 生命周期?下周来入职!


公众号名片 作者名片

2022 年什么会火?什么该学?本文正在参与“聊聊 2022 技术趋势”征文活动 」

作为一名移动端开发工程师,刚接触 Flutter 的时候,一定会有这样的疑问:Flutter 的生命周期是怎么样的?是如何处理生命周期的?我的 onCreate()[Android] 在哪里?viewDidLoad()[iOS] 呢? 我的业务逻辑应该放在哪里处理?初始化数据呢?希望看了这篇文章后,可以对你有一点小小的帮助。

安卓

如果你是一名安卓开发工程师,那么对于 Activity 生命周期肯定不陌生

  • onCreate
  • onStart
  • onResume
  • onPause
  • onStop
  • onDestroy

android_life_cycle

iOS

如果你是一名 iOS 开发工程师,那么 UIViewController 的生命周期肯定也已经很了解了。

  • viewDidLoad
  • viewWillAppear
  • viewDidAppear
  • viewWillDisappear
  • viewDidDisappear
  • viewDidUnload

ios_life_cycle

Flutter

知道了 Android 和 iOS 的生命周期,那么 Flutter 呢?有和移动端对应的生命周期函数么?如果之前你对 Flutter 有一点点了解的话,你会发现 Flutter 中有两个主要的 Widget:StatelessWidget(无状态)StatefulWidget(有状态)。本篇文章我们主要来介绍下 StatefulWidget,因为它有着和 Android 和 iOS 相似的生命周期。

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

不是生命周期但是却非常重要的几个概念

下面这些并不是生命周期的一部分,但是在生命周期中起到了很重要的作用。

  • mounted:是 State 中的一个重要属性,相当于一个标识,用来表示当前组件是否在树中。在 createState 后 initState 前,mounted 会被置为 true,表示当前组件已经在树中。调用 dispose 时,mounted 被置为 false,表示当前组件不在树中。
  • dirty:表示当前组件为脏状态,下一帧时将会执行 build 函数,调用 setState 方法或者执行 didUpdateWidget 方法后,组件的状态为 dirty。
  • clean:与 dirty 相对应,clean 表示组件当前的状态为干净状态,clean 状态下组件不会执行 build 函数。

stateful_widget_lifecycle 生命周期流程图

上图为 flutter 生命周期流程图

大致分为四个阶段

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

组件首次加载执行过程

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

  1. 创建一个 flutter 项目;
  2. 创建 count_widget.dart 中添加以下代码;
import 'package:flutter/material.dart';

class CountWidget extends StatefulWidget {
  CountWidget({Key key}) : super(key: key);

  @override
  _CountWidgetState createState() {
    print('count createState');
    return _CountWidgetState();
  }
}

class _CountWidgetState extends State<CountWidget> {
  int _count = 0;
  void _incrementCounter() {
    setState(() {
      print('count setState');
      _count++;
    });
  }

  @override
  void initState() {
    print('count initState');
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print('count didChangeDependencies');
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(CountWidget oldWidget) {
    print('count didUpdateWidget');
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print('count deactivate');
    super.deactivate();
  }

  @override
  void dispose() {
    print('count dispose');
    super.dispose();
  }

  @override
  void reassemble() {
    print('count reassemble');
    super.reassemble();
  }

  @override
  Widget build(BuildContext context) {
    print('count build');
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            '$_count',
            style: Theme.of(context).textTheme.headline4,
          ),
          Padding(
            padding: EdgeInsets.only(top: 100),
            child: IconButton(
              icon: Icon(
                Icons.add,
                size: 30,
              ),
              onPressed: _incrementCounter,
            ),
          ),
        ],
      ),
    );
  }
}
复制代码

上述代码把 StatefulWidget 的一些生命周期都进行了重写,并且在执行中都打印了标识,方便看到函数的执行顺序。

  1. 在 main.dart 中加载该组件。代码如下:
import 'package:flutter/material.dart';

import './pages/count_widget.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() {
    return _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: CountWidget(),
    );
  }
}
复制代码

这时 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 消失在模拟器上,日志如下:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      // body: CountWidget(),
    );
  }
}
复制代码
flutter: count reassemble
flutter: count deactivate
flutter: count dispose
复制代码

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

触发组件再次 build

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

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

set_state

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

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

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() {
    print('main createState');
    return _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage> {
  int mainCount = 0;

  void _changeMainCount() {
    setState(() {
      print('main setState');
      mainCount++;
    });
  }

  @override
  void initState() {
    print('main initState');
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print('main didChangeDependencies');
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(MyHomePage oldWidget) {
    print('main didUpdateWidget');
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print('main deactivate');
    super.deactivate();
  }

  @override
  void dispose() {
    print('main dispose');
    super.dispose();
  }

  @override
  void reassemble() {
    print('main reassemble');
    super.reassemble();
  }

  @override
  Widget build(BuildContext context) {
    print('main build');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () => _changeMainCount(),
            child: Text('mainCount = $mainCount'),
          ),
          CountWidget(),
        ],
      ),
    );
  }
}
复制代码

重新加载 app,可以看到打印日志如下:

father_widget_create_state

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。

import 'package:flutter/material.dart';

class AppLifecycle extends StatefulWidget {
  AppLifecycle({Key key}) : super(key: key);

  @override
  _AppLifecycleState createState() {
    print('sub createState');
    return _AppLifecycleState();
  }
}

class _AppLifecycleState extends State<AppLifecycle>
    with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    print('sub initState');
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    // TODO: implement didChangeAppLifecycleState
    super.didChangeAppLifecycleState(state);
    print('didChangeAppLifecycleState');
    if (state == AppLifecycleState.resumed) {
      print('resumed:');
    } else if (state == AppLifecycleState.inactive) {
      print('inactive');
    } else if (state == AppLifecycleState.paused) {
      print('paused');
    } else if (state == AppLifecycleState.detached) {
      print('detached');
    }
  }

  @override
  Widget build(BuildContext context) {
    print('sub build');
    return Container(
      child: Text('data'),
    );
  }
}
复制代码

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 被销毁。

除了 app 生命周期的方法,Flutter 还有一些其他不属于生命周期,但是也会在一些特殊时机被观察到的方法,如 didChangeAccessibilityFeatures(当前系统改变了一些访问性活动的回调)didHaveMemoryPressure(低内存回调)didChangeLocales(用户本地设置变化时调用,如系统语言改变)didChangeTextScaleFactor(文字系数变化) 等,如果有兴趣的话,可以去试一试。

总结

本篇文章主要介绍了 Widget 中的 StatefulWidget 的生命周期,以及 Flutter App 相关的生命周期。但是要切记,StatefulWidget 虽好,但也不要无脑的所有 Widget 全都用它,能使用 StatelessWidget 还是要尽量去使用 StatelessWidget(仔细想一下,这是为什么呢?)。好啦,看完本篇文章,你就是 Flutter 初级开发工程师了,可以去面试了(狗头保命)。

最后

真正坚持到最后的人,往往靠的不是短暂的激情,而是恰到好处的喜欢和投入。你还那么年轻,完全可以成为任何你想要成为的样子!

更多精彩请关注我们的公众号「百瓶技术」,有不定期福利呦!

猜你喜欢

转载自juejin.im/post/7056646298073563166