Flutter中State的生命周期

Flutter中State的生命周期

Flutter中State类(状态)一般与StatefulWidget配合使用,来实现Widget的更新。State负责维护StatefulWidget的状态,并保存其状态信息。当Widget的状态发生变化时,用户只需要调用setState()方法,Fultter引擎便会重新构建Widget树来更新UI。其中setState()方法更类似于一个消息机制,用于通知Flutter引擎Widget状态发生了变化,Flutter引擎收到通知后便会更新Widget树。

由于State管理的StatefulWidget的状态信息,并负责了StateWidget控件的更新,因此了解其的生命周期对于Flutter开发具有重要意义。在某些应用场景下我们需要知道State和Widget何时被构建,何时被销毁以及何时被更新等。

1. State简介

State类的接口如以下代码所示,其中我们开发中最常用的为build()和setState()方法,分别用于构建Widget和通知Flutter引擎更新Widget树。State类持有了一个Widget对象,通过该对象来重构Widget树,同时持有一个Widget的一个上下文,来获取该Widget在Widget树中的位置。

//State为一个模板虚类,必须继承才能够使用
@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
  T? _widget;  //State的实例中会持有一个Widget对象
  StatefulElement? _element;  //持有StatefulElement对象,构建Element树
  BuildContext get context {  //持有context对象,实际上为StatefulElement
    return _element!;
  }
  
    Widget build(BuildContext context);  //构建Widget树
    
    void initState(){}  //初始化State
    
    void didUpdateWidget(covariant T oldWidget) { }  //更新Widget
    
    void reassemble() {}  //重新装配Widget,一般在热重载时调用
    
    void setState(VoidCallback fn) {}  //更新State,后面详细解释
    
    void deactivate() {}  //注销Widget,将Widget从树中移除,不释放资源。
    
    void dispose() {}  //销毁Widget,将Widget从树中真正移除,并释放资源
      
    void didChangeDependencies() {}  //更改依赖,State依赖发生变化
}

在开发中,我们一般只需要重载build()方法来定义子Widget,同时使用setState()方法来通知Flutter引擎更新UI。其中build()方法完全由用户定义的,这里我们给出setState()的主要代码来探究setState()方法的运行机制,代码如下:

  //setState()方法的主要代码,略去了调试信息
  @protected
  void setState(VoidCallback fn) {  //传入一个回调函数fn
    final dynamic result = fn() as dynamic;
    _element!.markNeedsBuild();    //将Element标记为需要重新build()
  }
  
  //owner为BuildOwner类型,同样略去调试信息。
  void markNeedsBuild() {
    owner!.scheduleBuildFor(this);  //传入该Element,并进入Flutter引擎的调度序列,等待重构
  }

从setState()中的源码可以看出,传入的回调函数会被运行,但是不影响Flutter引擎的build()过程,换句话说,即使回调函数fn()为空,Flutter引擎也会去根据现有的值去更新UI。在代码层面以下两种书写方式是等价的。

//书写方式1:先更新UI值,再调用setState()方法
TextButton(
  child: Text('$_counter'),
  onPressed:(){
    ++_counter;  //先更新需要变动的UI值
    setState(() {});  //再调用setState()方法
  },
);

//书写方式2:在setState()方法体中更新UI值
TextButton(
  child: Text('$_counter'),
  onPressed:(){
    setState(() {
        ++_counter;  //在setState()方法中更新UI值
    });  
  },
);

但是为了代码的可读性,即让程序员知道我们在什么地方刷新了UI,还是会将需要更新UI的值放在setState()的回调函数体中。

2.State()的生命周期

清楚地知道State()的生命周期对使用Flutter开发是很有意义的。举例来说,如果在某些情况下我们需要用到Widget中的参数来完成对State<Widget>的初始化操作,我们就可以在initState()方法中通过value=widget.initValue的方式来进行初始化。如果采用State<Widget>的构造函数来进行初始化的话,就会造成代码的冗余和可读性变差的问题。另外,了解State何时被构建何时被销毁,对程序开发也具有重要的意义。

在上一节,我们以及较为详细的介绍了State类中的成员,这里给出一个表格来详细介绍State中方法的作用。

方法 作用
initState() 初始化State时调用,将State插入渲染树,只会调用一次
didChangeDependencies() State依赖对象变化时调用,如改变语言和主题时会调用该方法。
didUpdateWidget() 组件状态改变时调用,可能调用多次
build() 构建Widget
deactivate() 注销,将State移除渲染树,但是暂时不销毁
dispose() 销毁,将State从内存中移除
reassemble() 重组,当热面热加载时会被调用

下面我们将以一个简单的页面跳转的代码来探究Flutter的生命周期。代码由两个页面构成,分别为StateDemoPage1(页面1)和StateDemoPage2(页面2),他们都只有一个TextButton控件,StateDemoPage1实现了到StateDemoPage2的一个简单跳转,StateDemoPage2实现了一个点击更换Button颜色的逻辑,通过这个简单跳转我们来观察State()的生命周期,代码如下:

  //页面1代码
  class StateDemoPage1 extends StatefulWidget{

  const StateDemoPage1({
    Key key
  });

  @override
  State<StatefulWidget> createState() {
    return new _StateDemoPage1();
  }
}

class _StateDemoPage1 extends State<StateDemoPage1>{
  @override
  Widget build(BuildContext context) {
    print("_StateDemoPage1: build(创建Widget,构建Widget树)");
    return Scaffold(
      body: Center(
        child: TextButton(
          child: Text('跳转到下个页面'),
          //点击后计数器自增
          onPressed:(){
            Navigator.push(context, MaterialPageRoute(builder: (context){
              StateDemoPage2();
            }));
          },
          style: ButtonStyle(
            backgroundColor: MaterialStateProperty.resolveWith((states) => Colors.blue)
          )
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    print("_StateDemoPage1: initState(初始化State)");
  }

  @override
  void didUpdateWidget(covariant StateDemoPage1 oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("_StateDemoPage1: didUpdateWidget(更新Widget)");
  }

  @override
  void deactivate() {
    super.deactivate();
    print("_StateDemoPage1: deactive(注销Widget,将Widget从树中移除,不释放资源。)");
  }

  @override
  void dispose() {
    super.dispose();
    print("_StateDemoPage1: dispose(销毁Widget,将Widget从树中真正移除,并释放资源)");
  }

  @override
  void reassemble() {
    super.reassemble();
    print("_StateDemoPage1: reassemble(重组Widget)");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("_StateDemoPage1: didChangeDependencies(更改依赖,State依赖发生变化)");
  }

  _StateDemoPage1(){
    print("_StateDemoPage1: constructor(更改依赖,State依赖发生变化)");
  }
}
  
//页面2代码
  class StateDemoPage2 extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return new _StateDemoPage2();
  }
}

class _StateDemoPage2 extends State<StateDemoPage2>{
  Color mBackGroundColor = Colors.redAccent;
  @override
  Widget build(BuildContext context) {
    print("_StateDemoPage2: build(创建Widget,构建Widget树)");
    return Scaffold(
      body: Center(
        child: TextButton(
            child: Text('这里是第二个页面',
            style: TextStyle(
              color: Colors.white
            ),),
            onPressed: (){
              setState(() {
                mBackGroundColor=Colors.green;
              });
            },
            style: ButtonStyle(
                backgroundColor: MaterialStateProperty.resolveWith((states) => mBackGroundColor)
            )
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    print("_StateDemoPage2: initState(初始化State)");
  }

  @override
  void didUpdateWidget(covariant StateDemoPage2 oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("_StateDemoPage2: didUpdateWidget(更新Widget)");
  }

  @override
  void deactivate() {
    super.deactivate();
    print("_StateDemoPage2: deactive(注销Widget,将Widget从树中移除,不释放资源。)");
  }

  @override
  void dispose() {
    super.dispose();
    print("_StateDemoPage2: dispose(销毁Widget,将Widget从树中真正移除,并释放资源)");
  }

  @override
  void reassemble() {
    super.reassemble();
    print("_StateDemoPage2: reassemble(重组Widget)");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("_StateDemoPage2: didChangeDependencies(更改依赖,State依赖发生变化)");
  }

  _StateDemoPage2(){
    print("_StateDemoPage2: constructor(构造函数)");
  }
}

step1: 点击开始运行

页面1展示

页面1中State的初始化过程: 当运行页面1时,依次运行了_StateDemoPage1的:构造函数->initState()->didChangeDependencies()->build()。

_StateDemoPage1: constructor(构造函数)
_StateDemoPage1: initState(初始化State)
_StateDemoPage1: didChangeDependencies(更改依赖,State依赖发生变化)
_StateDemoPage1: build(创建Widget,构建Widget树)

页面1中State的热重载过程: 当我们对页面1进行热重载的时候,运行了_StateDemoPage1的:reassemble()->didUpdateWidget()->build()。

_StateDemoPage1: constructor(构造函数)
_StateDemoPage1: initState(初始化State)
_StateDemoPage1: didChangeDependencies(更改依赖,State依赖发生变化)
_StateDemoPage1: build(创建Widget,构建Widget树)
_StateDemoPage1: reassemble(重组Widget)
_StateDemoPage1: didUpdateWidget(更新Widget)
_StateDemoPage1: build(创建Widget,构建Widget树)

step2:在点击页面1中的跳转按钮后,跳转到页面2

页面2展示

页面2初始化过程:此时,页面2进入了初始化过程,其构建过程与页面1相同。而页面1为不可见状态,但是被没有进入销毁过程。终端输出如下:

_StateDemoPage2: constructor(构造函数)
_StateDemoPage2: initState(初始化State)
_StateDemoPage2: didChangeDependencies(更改依赖,State依赖发生变化)
_StateDemoPage2: build(创建Widget,构建Widget树)

step3:此时点击页面2,更换到绿色按钮

image

页面2的更新过程: 此时,调用了build()方法来更新了UI。

_StateDemoPage2: build(创建Widget,构建Widget树)

step4: TopBar上的点击返回

页面2的销毁过程: 此时,我们进入了销毁过程,依次调用了_StateDemoPage2的deactive()->dispose()方法来完成页面2的销毁。

_StateDemoPage2: deactive(注销Widget,将Widget从树中移除,不释放资源。)
_StateDemoPage2: dispose(销毁Widget,将Widget从树中真正移除,并释放资源)

StatefulWidget的生命周期如下图所示,我们可以大致分为3个阶段:

  • 初始化过程: constructor->initState()->didChangeDependencies()->build()
  • 更新过程: reassemble()->didUpdateWidget()
  • 销毁过程: deactive()->dispose()
State的生命周期

欢迎关注

猫枣编程

参考文献

[1] https://book.flutterchina.club/chapter3/

[2] https://blog.csdn.net/u011272795/article/details/82695920

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容