[Flutter] 10-Flutter的生命周期

作者: codeTao | 来源:发表于2020-07-05 18:46 被阅读0次

    本章介绍 Flutter 的组件,以及组件的生命周期。

    一、组件 Widget定义

    Flutter 中的组件与前端组件的理解和作用基本一致,但是没有一个明确的概念解释 Flutter 组件,这里借用前端的组件定义来解释 Flutter 组件的概念。

    一个 Flutter 组件,包含了组件的模板、样式和交互等内容,外部只要按照组件设定的属性、函数及事件处理等进行调用即可,完全不用考虑组件的内部实现逻辑。其中组件又包括无状态组件和有状态组件。

    • 无状态组件

    无状态组件,可以理解为将外部传入的数据转化为界面展示的内容,只会渲染一次

    • 有状态组件

    有状态组件,是定义交互逻辑和业务数据,可以理解为具有动态可交互的内容界面,会根据数据的变化进行多次渲染。

    二、生命周期

    在原生 Android 、原生 iOS 、前端 React 或者 Vue 都存在生命周期的概念,在 Flutter 中一样存在生命周期的概念,其基本概念和作用相似。 Flutter 中说的生命周期,也是指有状态组件,对于无状态组件生命周期只有 build 这个过程,也只会渲染一次,而有状态组件则比较复杂,下面我们就来看看有状态组件的生命周期过程。

    1)、生命周期的流转

    Flutter 中的生命周期,包含以下几个阶段:

    • createState ,该函数为 StatefulWidget 中创建 State 的方法,当 StatefulWidget 被调用时会立即执行 createState 。

    • initState ,该函数为 State 初始化调用,因此可以在此期间执行 State 各变量的初始赋值,同时也可以在此期间与服务端交互,获取服务端数据后调用 setState 来设置 State。
      注意:这个方法是重写父类的方法,必须调用super,因为父类中会进行一些其他操作;
      并且如果你阅读源码,你会发现这里有一个注解(annotation):@mustCallSuper

    @protected
    @mustCallSuper
    void initState() {
        assert(_debugLifecycleState == _StateLifecycle.created);
    }
    
    • didChangeDependencies ,该函数是在该组件依赖的 State 发生变化时,这里说的 State 为全局 State ,例如语言或者主题等,类似于前端 Redux 存储的 State 。这个方法在两种情况下会调用:
      情况一:调用initState会调用;
      情况二:从其他对象中依赖一些数据发生改变时,比如前面我们提到的InheritedWidget;
    • build ,主要是返回需要渲染的 Widget ,由于 build 会被调用多次,因此在该函数中只能做返回 Widget 相关逻辑,避免因为执行多次导致状态异常。

    • reassemble ,主要是提供开发阶段使用,在 debug 模式下,每次热重载都会调用该函数,因此在 debug 阶段可以在此期间增加一些 debug 代码,来检查代码问题。

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

    • deactivate ,在组件被移除节点后会被调用,如果该组件被移除节点,然后未被插入到其他节点时,则会继续调用 dispose 永久移除。

    • dispose ,永久移除组件,并释放组件资源。

    图1 生命周期流程图

    整个过程分为四个阶段:

    • 1.初始化阶段,包括两个生命周期函数 createState 和 initState;

    • 2.组件创建阶段,也可以称组件出生阶段,包括 didChangeDependencies 和 build;

    • 3.触发组件多次 build ,这个阶段有可能是因为 didChangeDependencies、setState 或者 didUpdateWidget 而引发的组件重新 build ,在组件运行过程中会多次被触发,这也是优化过程中需要着重需要注意的点;

    • 4.最后是组件销毁阶段,deactivate 和 dispose。

    2)、组件首次加载执行过程

    我们先实现一段代码,来看下组件在首次创建的执行过程是否是按照图 1 的流程。

    • 1、 在 lib 中 pages 下创建 test_stateful_widget.dart ;
    • 2、 在 test_stateful_widget.dart 添加如下代码:
    import 'package:flutter/material.dart';
    /// 创建有状态测试组件
    class TestStatefulWidget extends StatefulWidget {
      @override
      createState() {
        print('create state');
        return TestState();
      }
    }
    /// 创建状态管理类,继承状态测试组件
    class TestState extends State<TestStatefulWidget> {
      /// 定义 state [count] 计算器
      int count = 1;
      /// 定义 state [name] 为当前描述字符串
      String name = 'test';
      @override
      initState() {
        print('init state');
        super.initState();
      }
      @override
      didChangeDependencies() {
        print('did change dependencies');
        super.didChangeDependencies();
      }
      @override
      didUpdateWidget(TestStatefulWidget oldWidget) {
        count++;
        print('did update widget');
        super.didUpdateWidget(oldWidget);
      }
      @override
      deactivate() {
        print('deactivate');
        super.deactivate();
      }
      @override
      dispose() {
        print('dispose');
        super.dispose();
      }
      @override
      reassemble(){
        print('reassemble');
        super.reassemble();
      }
      /// 修改 state name
      void changeName() {
        setState(() {
          print('set state');
          this.name = 'flutter';
        });
      }
      @override
      Widget build(BuildContext context) {
        print('build');
        return Column(
          children: <Widget>[
            FlatButton(
              child: Text('$name $count'), // 使用 Text 组件显示描述字符和当前计算
              onPressed:()=> this.changeName(), // 点击触发修改描述字符 state name
            )
          ],
        );
      }
    }
    

    上述代码把有状态组件的一些生命周期函数都进行了重写,并且在执行中都打印了一些字符串标识,目的是可以看到该函数被执行。

    • 3、 然后在 main.dart 中加载该组件,代码如下:
    import 'package:flutter/material.dart';
    import 'package:flutter_liftcycle_demo/pages/test_stateful_widget.dart';
    
    /// APP 核心入口文件
    void main() => runApp(MyApp());
    /// MyApp 核心入口界面
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Flutter Lifecycle', // APP 名字
            theme: ThemeData(
              primarySwatch: Colors.blue, // APP 主题
            ),
            home: Scaffold(
                appBar: AppBar(
                  title: Text('首页'), // 页面名字
                ),
                body: Center(
                 child:
                  TestStatefulWidget(),
                )
            ));
      }
    }
    

    代码修改后,我们打开手机模拟器,然后运行该 App ,在输出控制台可以看到下面的运行打印日志信息。

    flutter: create state
    flutter: init state
    flutter: did change dependencies
    flutter: build
    flutter: reassemble
    flutter: did update widget
    flutter: build
    

    运行结果中,打印过程可以看到是按照我们上面图 1 的执行流程在运行的,但其中最值得关注的是 build 运行了两次。这是在开发模式下才会执行的过程,在正式环境是不会出现的,因为重新渲染成本非常大,这个问题可以使用打印 build 的调用堆栈即可发现。如果你要关闭两次 build 也可以实现,在 Flutter 框架中搜索 constants.dart 文件,并找到下面这行代码,将 defaultValue 从 false 修改为 true。

    const bool kReleaseMode = bool.fromEnvironment('dart.vm.product', defaultValue: true);
    

    其实这里会触发 didUpdateWidget 函数,是因为 TestStatefulWidget 组件是 MyApp 组件中的子组件,从而导致 MyApp 函数中的 build 触发子组件 didUpdateWidget 函数的执行,具体会在下面触发组件再次 build 中详细说明。

    3)、触发组件再次 build

    触发组件再次 build 有三种方式: 一个是 setState ,另一个是 didChangeDependencies ,再一个是 didUpdateWidget 。

    • setState 比较容易理解,在数据状态进行变化时,触发组件 build ,在上面的代码运行后的界面中,点击中间的text,如图 2 位置,就可以看到在调用 setState 后,会调用 build 一个方法。
    图 2 测试组件运行界面
    //点击中间的text
    flutter: set state
    flutter: build
    
    • didChangeDependencies ,你可以理解为本组件依赖的全局 state 的值发生了变化,例如前端的 redux 中的数据发生了变化,也会进行 build 操作。一般情况下我们会将一些比较基础的数据放到全局变量中,例如主题颜色、地区语言或者其他通用变量等。如果这些全局 state 发生状态变化则会触发该函数,而该函数之后就会触发 build 操作。

    • didUpdateWidget 触发 build 我们需要从代码层面来讲解下,现在我们需要设计两个组件,一个是我们刚实现的 TestStatefulWidget ,另外一个则是该组件的子组件,我们命名为SubStatefulWidget 。接下来我们在 TestStatefulWidget 加载该组件,在头部 import 该组件,然后将 build 中的代码修改为下面:

    @override
    Widget build(BuildContext context) {
      print('build');
      return Column(
        children: <Widget>[
          FlatButton(
            child: Text('$name $count'), // 使用 Text 组件显示描述字符和当前计算
            onPressed:()=> this.changeName(), // 点击触发修改描述字符 state name
          ),
          SubStatefulWidget() // 加载子组件
        ],
      );
    }
    

    接下来我们实现 SubStatefulWidget 子组件的代码,和父组件基本相似,只是在打印处都加了 sub ,其次 build 实现逻辑也修改了,具体代码如下:

    import 'package:flutter/material.dart';
    /// 创建子组件类
    class SubStatefulWidget extends StatefulWidget {
      @override
      createState() {
        print('sub create state');
        return SubState();
      }
    }
    /// 创建子组件状态管理类
    class SubState extends State<SubStatefulWidget> {
      String name = 'sub test';
      @override
      initState() {
        print('sub init state');
        super.initState();
      }
      @override
      didChangeDependencies() {
        print('sub did change dependencies');
        super.didChangeDependencies();
      }
      @override
      didUpdateWidget(SubStatefulWidget oldWidget) {
        print('sub did update widget');
        super.didUpdateWidget(oldWidget);
      }
      @override
      deactivate() {
        print('sub deactivate');
        super.deactivate();
      }
      @override
      dispose() {
        print('sub dispose');
        super.dispose();
      }
      @override
      reassemble(){
        print('sub reassemble');
        super.reassemble();
      }
      @override
      Widget build(BuildContext context) {
        print('sub build');
        return Text('subname $name'); // 使用Text组件显示当前name state
      }
    }
    

    代码实现完成后,我们再重新加载 App ,可以看到如下运行日志信息。

    flutter: create state
    flutter: init state
    flutter: did change dependencies
    flutter: build
    flutter: sub create state
    flutter: sub init state
    flutter: sub did change dependencies
    flutter: sub build
    flutter: reassemble
    flutter: sub reassemble
    flutter: did update widget
    flutter: build
    flutter: sub did update widget
    flutter: sub build
    
    • 加载 TestStatefulWidget 组件,四个状态函数 createState、initState、didChangeDependencies 和 build;

    • 加载 SubStatefulWidget 组件,四个状态函数 createState、initState、didChangeDependencies 和 build;

    • TestStatefulWidget 进行二次 build ,因为父组件需要重新 build 触发子组件的 didUpdateWidget ,didUpdateWidget 则触发 build。

    为了验证上面逻辑,我们现在再次点击图 3 中的红色部分,来触发 TestStatefulWidget 组件的 build ,看下是否会触发子组件的 didUpdateWidget 和 build。

    图 3 增加子组件界面点击指示图

    在运行日志窗口可以看到增加了下面的日志信息。

    flutter: set state
    flutter: build
    flutter: sub did update widget
    flutter: sub build
    

    这就说明了父组件的变化会引发子组件的 build ,虽然子组件没有任何的改动。这点如果是在前端的话,是需要使用 shouldUpdateComponent ,来介绍重新构建,不过在 Flutter 中是没有该功能来减少重新 build 的。

    4)、组件销毁触发

    在上面的代码基础上,我们直接在 TestStatefulWidget 组件中注释子组件 SubStatefulWidget 的调用,然后热重载即可看到下面的日志信息(请注意一定是需要热重载才会有效果,主要目的是一开始加载了该组件,后面再去掉该组件触发)。

    flutter: reassemble
    flutter: sub reassemble
    flutter: did update widget
    flutter: build
    flutter: sub deactivate
    flutter: sub dispose
    

    5) 补充: 生命周期属性

    其实生命周期中initState方法前后还有两个属性调用,如下图:

    1、mounted是State内部设置的一个属性,事实上我们不了解它也可以,但是如果你想深入了解它,会对State的机制理解更加清晰;

    • 很多资料没有提到这个属性,但是我这里把它列出来,是内部设置的,不需要我们手动进行修改;
    mounted属性源码

    2、dirty state的含义是脏的State

    • 它实际是通过一个Element的东西的属性来标记的;
    • 将它标记为dirty会等待下一次的重绘检查,强制调用build方法来构建我们的Widget;

    3、clean state的含义是干净的State

    • 它表示当前build出来的Widget,下一次重绘检查时不需要重新build;

    相关文章

      网友评论

        本文标题:[Flutter] 10-Flutter的生命周期

        本文链接:https://www.haomeiwen.com/subject/ycjvqktx.html