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的生命周期

    Flutter 的生命周期分为两个部分: Widget的生命周期 App的生命周期 Flutter里的Widget...

  • Flutter 页面生命周期

    Flutter 页面生命周期就是 Flutter 页面组件 Widget 的生命周期。 Flutter 有两种组件...

  • Flutter 的生命周期(转载)

    一,概述 Flutter 的生命周期分为两个部分: Widget 的生命周期 App 的生命周期 二,Widget...

  • Flutter 的生命周期

    Flutter 的生命周期Flutter 的生命周期分为两个部分: Widget 的生命周期App 的生命周期Fl...

  • Flutter 生命周期

    前提 Flutter 的生命周期分为两个部分: 1.Widget 的生命周期2.App 的生命周期 Widget的...

  • 监听Flutter的生命周期

    监听Flutter的生命周期 要了解Widget,就需要先知道Widget的生命周期。 这里说到的生命周期分为两个...

  • Flutter Widget生命周期详解

    1 Widget 简介 在Flutter中,一切皆是Widget(组件),Widget的功能是“描述一个UI元素的...

  • Flutter的生命周期

    Flutter的生命周期 1、widget的生命周期 1.1 StatelessWidget 的生命周期(只有bu...

  • Flutter生命周期及监听状态

    Flutter Widget生命周期分为3个阶段: 1.创建: 构造方法:Flutter通过StatefulWid...

  • [Flutter] 10-Flutter的生命周期

    本章介绍 Flutter 的组件,以及组件的生命周期。 一、组件 Widget定义 Flutter 中的组件与前端...

网友评论

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

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