Flutter Widget生命周期详解

作者: Joker_Wan | 来源:发表于2020-08-10 17:41 被阅读0次

    1 Widget 简介

    在Flutter中,一切皆是Widget(组件),Widget的功能是“描述一个UI元素的配置数据”,它就是说,Widget其实并不是表示最终绘制在设备屏幕上的显示元素,而它只是描述显示元素的一个配置数据。

    实际上,Flutter中真正代表屏幕上显示元素的类是 Element,也就是说Widget 只是描述 Element 的配置数据。并且一个 Widget 可以对应多个 Element,因为同一个 Widget 对象可以被添加到 UI树的不同部分,而真正渲染时,UI树的每一个 Element 节点都会对应一个 Widget 对象。

    其中组件又包括无状态组件和有状态组件。

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

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

    StatelessWidget 和 StatefulWidget 都是直接继承自 Widget 类,而这两个类也正是 Flutter 中非常重要的两个抽象类,它们引入了两种 Widget 模型。

    2 两种Widget模型

    2.1 StatelessWidget

    @override
    StatelessElement createElement() => new StatelessElement(this);
    

    StatelessWidget相对比较简单,它继承自Widget类,重写了createElement()方法。

    StatelessElement 间接继承自Element类,与StatelessWidget相对应(作为其配置数据)。

    StatelessWidget用于不需要维护状态的场景,并且只会被渲染一次,它通常在build方法中通过嵌套其它Widget来构建UI,在构建过程中会递归的构建其嵌套的Widget。

    2.2 StatefulWidget

    abstract class StatefulWidget extends Widget {
      const StatefulWidget({ Key key }) : super(key: key);
    
      @override
      StatefulElement createElement() => new StatefulElement(this);
    
      @protected
      State createState();
    }
    

    和StatelessWidget一样,StatefulWidget也是继承自Widget类,并重写了createElement()方法,不同的是返回的Element 对象并不相同;另外StatefulWidget类中添加了一个新的接口createState()。

    StatefulElement 间接继承自Element类,与StatefulWidget相对应(作为其配置数据)。StatefulElement中可能会多次调用createState()来创建状态(State)对象。

    createState() 用于创建和StatefulWidget相关的状态,它在StatefulWidget的生命周期中可能会被多次调用。例如,当一个StatefulWidget同时插入到widget树的多个位置时,Flutter framework就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。

    2.2.1 State

    一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态,State中的保存的状态信息可以:

    • 在widget 构建时可以被同步读取。
    • 在widget生命周期中可以被改变,当State被改变时,可以手动调用其setState()方法通知Flutter framework状态发生改变,Flutter framework在收到消息后,会重新调用其build方法重新构建widget树,从而达到更新UI的目的。

    State中有两个常用属性:

    1. widget,它表示与该State实例关联的widget实例,由Flutter framework动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI树上的某一个节点的widget实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果widget被修改了,Flutter framework会动态设置State.widget为新的widget实例。
    2. context,StatefulWidget对应的BuildContext,作用同StatelessWidget的BuildContext,表示当前widget在widget树中的上下文,每一个widget都会对应一个context对象(因为每一个widget都是widget树上的一个节点)。

    3 生命周期

    3.1 组件生命周期

    Flutter 中说的生命周期,是独指有状态组件的生命周期,对于无状态组件生命周期只有一次 build 这个过程,也只会渲染一次。有状态组件的生命周期如下图:

    Flutter-Widget生命周期.png

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

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

    • initState ,该函数为 State 初始化调用,因此可以在此期间执行 State 各变量的初始赋值,同时也可以在此期间与服务端交互,获取服务端数据后调用 setState 来设置 State。

    • didChangeDependencies ,当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个InheritedWidget,然后在之后的build() 中InheritedWidget发生了变化,那么此时InheritedWidget的子widget的didChangeDependencies()回调都会被调用。典型的场景是当系统语言Locale或应用主题改变时,Flutter framework会通知widget调用此回调。

    • build ,主要是返回需要渲染的 Widget ,由于 build 会被调用多次,因此在该函数中只能做返回 Widget 相关逻辑,避免因为执行多次导致状态异常。

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

    • didUpdateWidget ,在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。正如之前所述,Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。父组件发生 build 的情况下,子组件该方法才会被调用,其次该方法调用之后一定会再调用本组件中的 build 方法。

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

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

    Flutter 生命周期的整个过程可以分为四个阶段

    1. 初始化阶段:createState 和 initState
    2. 组件创建阶段:didChangeDependencies 和 build
    3. 触发组件 build:didChangeDependencies、setState 或者didUpdateWidget 都会引发的组件重新 build
    4. 组件销毁阶段:deactivate 和 dispose

    3.2 组件首次加载过程

    我们通过代码来看下组件首次加载执行的生命周期过程

    在 lib 中创建 test_stateful_widget.dart

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    
    class TestStatefulWidget extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        print('create state');
        return TestState();
      }
    }
    
    class TestState extends State<TestStatefulWidget> {
      /// 定义 state [count] 计算器
      int count = 1;
    
      /// 定义 state [name] 为当前描述字符串
      String name = 'test';
    
      @override
      void initState() {
        print('init state');
        super.initState();
      }
    
      @override
      void didChangeDependencies() {
        print('did change dependencies');
        super.didChangeDependencies();
      }
    
      @override
      void didUpdateWidget(TestStatefulWidget oldWidget) {
        count++;
        print('did update widget');
        super.didUpdateWidget(oldWidget);
      }
    
      @override
      void deactivate() {
        print('deactivate');
        super.deactivate();
      }
    
      @override
      void dispose() {
        print('dispose');
        super.dispose();
      }
    
      @override
      void reassemble() {
        print('reassemble');
        super.reassemble();
      }
    
      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'), onPressed: () => this.changeName())
          ],
        );
      }
    }
    
    

    在 main.dart 中加载该组件

    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Test',
          theme: ThemeData(
              primaryColor: Colors.amberAccent
          ),
          home: Scaffold(
            appBar: AppBar(
              title: Text('生命周期测试'),
            ),
            body: Center(
              child: TestStatefulWidget(),
            ),
          ),
        );
      }
    }
    

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

    I/flutter ( 7729): create state
    I/flutter ( 7729): init state
    I/flutter ( 7729): did change dependencies
    I/flutter ( 7729): build
    

    当我们点击 Android Studio 中的 Hot Reload 按钮(黄色小闪电 ⚡️)时控制台输出信息如下:


    I/flutter ( 7729): reassemble
    I/flutter ( 7729): did update widget
    I/flutter ( 7729): build
    

    3.3 触发组件再次 build

    触发组件再次 build 有三种方式

    1. setState
    2. didChangeDependencies
    3. didUpdateWidget

    setState的场景开发中你那个经常遇到,在数据状态变化时触发组件 build,在上面的代码运行后的界面中点击 test 文本按钮,然后观察控制台输出如下:

    I/flutter ( 7729): set state
    I/flutter ( 7729): build
    

    didChangeDependencies场景:一般情况下我们会将一些比较基础的数据放到全局变量中,例如主题颜色、地区语言或者其他通用变量等。如果这些全局 state 发生状态变化则会触发该函数,而该函数之后就会触发 build 操作。

    接下来看下didUpdateWidget 触发 build 的场景:
    创建一个组件 SubStatefulWidget 继承 TestStatefulWidget

    class SubStatefulWidget extends TestStatefulWidget {
      @override
      State<StatefulWidget> createState() {
        print('sub create state');
        return SubState();
      }
    }
    
    class SubState extends State<SubStatefulWidget> {
    
      @override
      void initState() {
        print('sub init state');
        super.initState();
      }
    
      @override
      void didChangeDependencies() {
        print('sub did change dependencies');
        super.didChangeDependencies();
      }
    
      @override
      void didUpdateWidget(TestStatefulWidget oldWidget) {
        print('sub did update widget');
        super.didUpdateWidget(oldWidget);
      }
    
      @override
      void deactivate() {
        print('sub deactivate');
        super.deactivate();
      }
    
      @override
      void dispose() {
        print('sub dispose');
        super.dispose();
      }
    
      @override
      void reassemble() {
        print('sub reassemble');
        super.reassemble();
      }
    
      void changeName() {
        setState(() {
          print('sub set state');
        });
      }
    
      @override
      Widget build(BuildContext context) {
        print('sub build');
        return Text('SubStatefulWidget');
      }
    }
    

    接着在 TestStatefulWidget 加载该组件

      @override
      Widget build(BuildContext context) {
        print('build');
        return Column(
          children: <Widget>[
            FlatButton(
                child: Text('$name $count'), onPressed: () => this.changeName()),
            SubStatefulWidget()
          ],
        );
      }
    

    重新加载 APP,观看控制台输出如下:

    I/flutter ( 8464): create state
    I/flutter ( 8464): init state
    I/flutter ( 8464): did change dependencies
    I/flutter ( 8464): build
    I/flutter ( 8464): sub create state
    I/flutter ( 8464): sub init state
    I/flutter ( 8464): sub did change dependencies
    I/flutter ( 8464): sub build
    

    上面日志先后打印了 TestStatefulWidget 与 SubStatefulWidget 四个状态函数 createState、initState、didChangeDependencies 和 build。当我们再次点击 test 文本按钮,观察控制台输出如下:

    I/flutter ( 9425): set state
    I/flutter ( 9425): build
    I/flutter ( 9425): sub did update widget
    I/flutter ( 9425): sub build
    

    通过上面打印的日志我们可知,父组件调用setState不仅会触发自己 build,还会引发子组件重新 build ,虽然子组件没有任何改动。

    3.4 触发组件销毁

    在3.3的代码的基础上,我们直接在 TestStatefulWidget 组件中注释子组件 SubStatefulWidget 的调用

      @override
      Widget build(BuildContext context) {
        print('build');
        return Column(
          children: <Widget>[
            FlatButton(
                child: Text('$name $count'), onPressed: () => this.changeName()),
    //        SubStatefulWidget()
          ],
        );
      }
    

    然后点击 Android Studio 上的 Hot Reload 按钮观察控制台输出如下信息:

    I/flutter ( 9425): reassemble
    I/flutter ( 9425): sub reassemble
    I/flutter ( 9425): did update widget
    I/flutter ( 9425): build
    I/flutter ( 9425): sub deactivate
    I/flutter ( 9425): sub dispose
    

    可以看到 SubStatefulWidget 被销毁

    4 总结

    1. Flutter 中 Widget 分为两种:StatelessWidget 和 StatefulWidget
    2. Flutter 生命周期的整个过程可以分为四个阶段:初始化、组件创建、触发组件 build、组件销毁
    3. StatelessWidget 用于不需要维护状态的场景,只会 build 一次
    4. StatefulWidget 会被多次触发 build 函数,触发函数是setState、didChangeDependencies、didUpdateWidget
    5. 父组件调用setState不仅会触发自己 build,还会引发子组件重新 build ,虽然子组件没有任何改动

    相关文章

      网友评论

        本文标题:Flutter Widget生命周期详解

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