Flutter Widget_APP生命周期

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 相关逻辑,避免因为执行多次导致状态异常。在 build 之后还有个回调 addPostFrameCallback,在当前帧绘制完成后会回调,注册之后不能被解注册并且只会回调一次;addPostFrameCallback是 SchedulerBinding 的方法;由于 mixin WidgetsBinding on SchedulerBinding,所以添加这个回调有两种方式:SchedulerBinding.instance.addPostFrameCallback((_) => {});或者WidgetsBinding.instance.addPostFrameCallback((_) => {});
  • 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 stat
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 App 生命周期监听

在 Flutter 中,可以利用 WidgetsBindingObserver 类来监听App 生命周期


abstract class WidgetsBindingObserver {
  // 页面 pop
  Future<bool> didPopRoute() => Future<bool>.value(false);
  // 页面 push
  Future<bool> didPushRoute(String route) => Future<bool>.value(false);
  // 系统窗口相关改变回调,如旋转
  void didChangeMetrics() { }
  // 文本缩放系数变化
  void didChangeTextScaleFactor() { }
  // 系统亮度变化
  void didChangePlatformBrightness() { }
  // 本地化语言变化
  void didChangeLocales(List<Locale> locale) { }
  //App 生命周期变化
  void didChangeAppLifecycleState(AppLifecycleState state) { }
  // 内存警告回调
  void didHaveMemoryPressure() { }
  //Accessibility 相关特性回调
  void didChangeAccessibilityFeatures() {}
}

其中didChangeAppLifecycleState(AppLifecycleState state)就是用来监听App生命周期。在 didChangeAppLifecycleState 回调函数中,有一个参数类型为 AppLifecycleState 的枚举类,这个枚举类是 Flutter 对 App 生命周期状态的封装。它的常用状态包括 resumed、inactive、paused 这三个。

  • resumed:可见的,并能响应用户的输入。
  • inactive:处在不活动状态,无法处理用户响应。
  • paused:不可见并不能响应用户的输入,但是在后台继续活动中。

当切换前、后台时,App 状态如下:

  • 从后台切入前台: AppLifecycleState.paused -> AppLifecycleState.inactive -> AppLifecycleState.resumed
  • 从前台退回后台:AppLifecycleState.resumed -> AppLifecycleState.inactive -> AppLifecycleState.paused

5 帧绘制回调

5.1 addPostFrameCallback

单次 Frame 绘制回调,会在当前 Frame 绘制完成后进行回调,并且只会回调一次,如果要再次监听则需要再设置一次。

WidgetsBinding.instance.addPostFrameCallback((_){
  print(" 单次 Frame 绘制回调 ");
});

5.2 addPersistentFrameCallback

实时 Frame 绘制回调,会在每次绘制 Frame 结束后进行回调,可以用做 FPS 监测。

WidgetsBinding.instance.addPersistentFrameCallback((_){
  print(" 实时 Frame 绘制回调");
});

6 总结

  1. Flutter 中 Widget 分为两种:StatelessWidget 和 StatefulWidget
  2. Flutter 生命周期的整个过程可以分为四个阶段:初始化、组件创建、触发组件 build、组件销毁
  3. StatelessWidget 用于不需要维护状态的场景,只会 build 一次
  4. StatefulWidget 会被多次触发 build 函数,触发函数是setState、didChangeDependencies、didUpdateWidget
  5. 父组件调用setState不仅会触发自己 build,还会引发子组件重新 build ,虽然子组件没有任何改动
  6. 可以利用 WidgetsBindingObserver 类来监听App 生命周期
  7. 帧绘制回调有单次 Frame 绘制回调(addPostFrameCallback)和实时 Frame 绘制回调(addPersistentFrameCallback)
THE END
喜欢就支持一下吧
点赞15赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容