乐趣区

关于前端:面试官知道-Flutter-生命周期下周来入职



作为一名挪动端开发工程师,刚接触 Flutter 的时候,肯定会有这样的疑难:Flutter 的生命周期是怎么样的?是如何解决生命周期的?我的 onCreate()[Android] 在哪里?viewDidLoad()[iOS] 呢?我的业务逻辑应该放在哪里解决?初始化数据呢?心愿看了这篇文章后,能够对你有一点小小的帮忙。

安卓

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

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

iOS

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

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

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 函数。

上图为 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,后果如下图:

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,能够看到打印日志如下:

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 是能够接管到这些告诉,但有个别情况下是无奈接管到告诉的,比方用户关机等。它的四种生命周期状态枚举源码中有具体的介绍和阐明,上面附上源码以及简略的翻译阐明。

  • 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 高级开发工程师了,能够去面试了(狗头保命)。

最初

真正保持到最初的人,往往靠的不是短暂的激情,而是恰到好处的喜爱和投入。你还那么年老,齐全能够成为任何你想要成为的样子!

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

退出移动版