Flutter之生命周期(更新中)

3,214 阅读9分钟

和谐学习!不急不躁!!我是你们的老朋友小青龙~

前言

生命周期,就是系统在程序的不同阶段(比如初始化、即将出现、销毁等)给我们返回的一个个方法,简单来说【生命周期】就是【系统回调方法】

有过iOS开发的小伙伴都知道,在iOS里,生命周期方法有:

  • init(init初始化)

  • awakeFromNib(Nib加载成功)

  • loadView(加载view)

  • viewDidLoad(载入完成)

  • viewWillAppear:(BOOL)animated(视图将出现在屏幕之前)

  • viewWillLayoutSubviews(将要对子视图进行调整)

  • viewDidLayoutSubviews(对子视图进行调整完毕)

  • viewDidAppear:(BOOL)animated(视图已在屏幕上渲染完成)

  • viewWillDisappear:(BOOL)animated(视图将被从屏幕上移除)

  • viewDidDisappear:(BOOL)animated(视图已经被从屏幕上移除)

  • dealloc(视图被销毁,这里释放)

  • didReceiveMemoryWarning(内存警告)

而安卓里也有对应的生命周期方法。

在Flutter有这么一句话:万物皆Widget。那么【Widget】又有哪些生命周期方法呢?

1、生命周期方法

Widget根据有无状态,分为

  • StatefulWidget(有状态)

  • StatelessWidget(无状态)

1.1、StatelessWidget生命周期方法

StatelessWidget是「无状态Widget」,意味着一旦创建就不能修改,所以它只有2个方法

  • 构造方法
  • build

image.png

1.2、StatefulWidget生命周期方法

StatefulWidget是「有状态Widget」,它可以通过【setState】来刷新Widget结构,所以它的生命周期方法相对就多一些:

  • 构造方法

  • createState方法

  • initState方法

  • didChangeDependencies方法(层级发生变化时被调用)

  • didUpdateWidget方法(数据发生变化时被调用)

  • build方法

  • setState方法(需要代码主动调用)

  • deactivate方法(组件移除的时候被调用)

  • dispose方法(Widget销毁的时候调用)

我写了两个测试页面如下(代码放在后面):

image.png

当我点击左边【红色字体】,会跳转进入右边页面,此刻控制台打印:

image.png

此刻,我点击【蓝色按钮+】,页面上数字由0变为1,控制台打印如下:

image.png

说明setState方法会触发build方法又一次调用

此刻,我点击左上角【返回】按钮后页面成功返回,再看看控制台:

image.png

所以【StatefulWidget】生命周期方法,执行顺序如下:

  1. 构造方法

  2. createState方法

  3. initState方法

  4. didChangeDependencies方法

  5. build方法(第一次默认执行,setState方法也会触发build)

  6. setState方法(需要代码主动调用)

  7. deactivate方法(组件移除的时候被调用)

  8. dispose方法(Widget销毁的时候调用)

这里少了对didUpdateWidget方法的分析,这个方法是跟着父Widget的setState而变化的,简单来说:

  • 父widget中调用【setState】方法,子widget的【didUpdateWidget】方法就会被调用。

2、测试代码

main.dart

import 'package:dart_life_cycle/page_two.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MineAPP(),
    );
  }
}

class MineAPP extends StatefulWidget {
  const MineAPP({Key? key}) : super(key: key);

  @override
  _MineAPPState createState() => _MineAPPState();
}

class _MineAPPState extends State<MineAPP> {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: GestureDetector(
      child: const Text('点我啊'),
      onTap: () {
        Navigator.of(context).push(MaterialPageRoute(
            builder: (BuildContext context) => PageTwo(
                  title: '',
                )));
      },
    ));
  }
}

page_two.dart

import 'package:flutter/material.dart';

class PageTwo extends StatefulWidget {
  final String title;
  PageTwo({Key? key, required this.title}) : super(key: key) {
    print('构造方法被调用');
  }

  @override
  _PageTwoState createState() {
    print('createState方法被调用');
    return _PageTwoState();
  }
}

class _PageTwoState extends State<PageTwo> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    print('build方法被调用');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  @override
  initState() {
    print('initState方法被调用');
    super.initState();
  }

  @override
  void setState(VoidCallback fn) {
    print('\n\nsetState方法被调用');
    super.setState(fn);
  }

  @override
  void didChangeDependencies() {
    print('didChangeDependencies方法被调用');
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(covariant PageTwo oldWidget) {
    print('didUpdateWidget方法被调用');
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print('deactivate方法被调用');
    super.deactivate();
  }

  @override
  void dispose() {
    print('dispose方法被调用');
    super.dispose();
  }
}

3、didUpdateWidget方法

当数据发生变化时,调用build方法之前,会调用didUpdateWidget方法。

测试代码

import 'package:flutter/material.dart';

/// WidgetA
class WidgetA extends StatefulWidget {
  const WidgetA({Key? key}) : super(key: key);

  @override
  _WidgetAState createState() => _WidgetAState();
}

class _WidgetAState extends State<WidgetA> {
  late int count = 0;

  @override
  Widget build(BuildContext context) {
    print('WidgetA - build方法被调用');
    return Scaffold(
      appBar: AppBar(
        title: const Text('测试didChangeDependencies'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            IconButton(
                onPressed: () {
                  setState(() {
                    count++;
                  });
                },
                icon: const Icon(Icons.eleven_mp)),
            Text('A显示$count'),
            // const WidgetB(),
            Container(
              alignment: Alignment.center,
              width: 300,
              height: 300,
              color: Colors.red,
              child: WidgetB(count: count),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void didUpdateWidget(covariant WidgetA oldWidget) {
    print('WidgetA - didUpdateWidget方法被调用');
    super.didUpdateWidget(oldWidget);
  }
}

/// WidgetB
class WidgetB extends StatefulWidget {
  final int count;
  const WidgetB({Key? key, required this.count}) : super(key: key);

  @override
  _WidgetBState createState() => _WidgetBState();
}

class _WidgetBState extends State<WidgetB> {
  @override
  Widget build(BuildContext context) {
    print('WidgetB - build方法被调用');
    return Container(
        width: 200,
        height: 200,
        color: Colors.yellow,
        child: Column(
          children: [
            Text('B显示${widget.count}'),
            WidgetC(count: widget.count),
          ],
        ));
  }

  @override
  void didUpdateWidget(covariant WidgetB oldWidget) {
    print('WidgetB - didUpdateWidget方法被调用');
    super.didUpdateWidget(oldWidget);
  }
}

/// WidgetC
class WidgetC extends StatefulWidget {
  final int count;
  const WidgetC({Key? key, required this.count}) : super(key: key);

  @override
  _WidgetCState createState() => _WidgetCState();
}

class _WidgetCState extends State<WidgetC> {
  @override
  Widget build(BuildContext context) {
    print('WidgetC - build方法被调用');
    return Container(
      child: Text('C显示${widget.count}'),
      alignment: Alignment.center,
      width: 100,
      height: 100,
      color: Colors.blueGrey,
    );
  }

  @override
  void didUpdateWidget(covariant WidgetC oldWidget) {
    print('WidgetC - didUpdateWidget方法被调用');
    super.didUpdateWidget(oldWidget);
  }
}

代码中,通过点击WidgetA上的按钮,改变count值,count值是子部件WidgetB和WidgetC构造函数的参数,所以当WidgetA改变count值并调用setState方法的时候,WidgetB和WidgetC也跟着刷新。

运行效果:

IMB_UZqy7H.GIF

4、didChangeDependencies方法

当widget层级发生变化时,调用build方法之前,会调用didChangeDependencies方法。

测试代码

import 'package:flutter/material.dart';

/// WidgetA
class WidgetA extends StatefulWidget {
  const WidgetA({Key? key}) : super(key: key);

  @override
  _WidgetAState createState() => _WidgetAState();
}

class _WidgetAState extends State<WidgetA> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    print('WidgetA - build方法被调用');
    return Scaffold(
      appBar: AppBar(
        title: const Text('测试didChangeDependencies'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            IconButton(
                onPressed: () {
                  setState(() {
                    count++;
                  });
                },
                icon: const Icon(Icons.eleven_mp)),
            Container(
              alignment: Alignment.center,
              width: 300,
              height: 300,
              color: Colors.red,
              child: count % 2 == 0
                  ? (Container(
                      width: 50,
                      height: 50,
                      color: Colors.blue,
                    ))
                  : const WidgetB(),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void didChangeDependencies() {
    print('WidgetA - didChangeDependencies方法被调用');
    super.didChangeDependencies();
  }
}

/// WidgetB
class WidgetB extends StatefulWidget {
  const WidgetB({Key? key}) : super(key: key);

  @override
  _WidgetBState createState() => _WidgetBState();
}

class _WidgetBState extends State<WidgetB> {
  @override
  Widget build(BuildContext context) {
    print('WidgetB - build方法被调用');
    return Container(
      width: 200,
      height: 200,
      color: Colors.yellow,
      alignment: Alignment.bottomCenter,
      child: const WidgetC(),
    );
// return;
  }

  @override
  void didChangeDependencies() {
    print('WidgetB - didChangeDependencies方法被调用');
    super.didChangeDependencies();
  }
}

/// WidgetC
class WidgetC extends StatefulWidget {
  const WidgetC({Key? key}) : super(key: key);

  @override
  _WidgetCState createState() => _WidgetCState();
}

class _WidgetCState extends State<WidgetC> {
  @override
  Widget build(BuildContext context) {
    print('WidgetC - build方法被调用');
    return Container(
      alignment: Alignment.center,
      width: 100,
      height: 100,
      color: Colors.blueGrey,
    );
  }

  @override
  void didChangeDependencies() {
    print('WidgetC - didChangeDependencies方法被调用');
    super.didChangeDependencies();
  }
}

运行效果:

IMB_nrjhJw.GIF

command+点击进入:

image.png

image.png

先看注释

Called when a dependency of this [State] object changes.

For example, if the previous call to [build] referenced an
[InheritedWidget] that later changed, the framework would call this
method to notify this object about the change.

This method is also called immediately after [initState]. It is safe to
call [BuildContext.dependOnInheritedWidgetOfExactType] from this method.

Subclasses rarely override this method because the framework always
calls [build] after a dependency changes. Some subclasses do override
this method because they need to do some expensive work (e.g., network
fetches) when their dependencies change, and that work would be too
expensive to do for every build.

// 百度翻译
此[状态]对象的依赖项更改时调用。

例如,如果对[build]的上一次调用引用了
[InheritedWidget]后来发生了变化,框架将调用它
方法将更改通知此对象。

此方法也会在[initState]之后立即调用。这样做是安全的
从此方法调用[BuildContext.dependOnInheritedWidgetOfExactType]。

子类很少重写此方法,因为框架总是
在依赖项更改后调用[build]。有些子类确实覆盖了
这是因为他们需要做一些昂贵的工作(例如,网络)
当它们的依赖项发生变化时,这项工作将变得非常困难
每次构建都很昂贵。

所以我们可以得知,

5、setState源码解读

前面我们了解到,StatefulWidget里调用setState方法,会主动触发异常build的回调,那么setState的底层源码是怎么一个过程呢?它的回调函数是什么时候被调用呢?

我们来看到setState调用的地方,点击它进入源码:

image.png

image.png

5.1、先看英文注释:

/** 摘取了部分注释 */

1、Notify the framework that the internal state of this object has changed.

2、The provided callback is immediately called synchronously. It must not return a future (the callback cannot be `async`), since then it would be unclear when the state was actually being set.

3、Calling [setState] notifies the framework that the internal state of this
object has changed in a way that might impact the user interface in this
subtree, which causes the framework to schedule a [build] for this [State]
object.

4、 If you just change the state directly without calling [setState], the
 framework might not schedule a [build] and the user interface for this
 subtree might not be updated to reflect the new state.

 5、Generally it is recommended that the `setState` method only be used to
 wrap the actual changes to the state, not any computation that might be
 associated with the change. For example, here a value used by the [build]
 function is incremented, and then the change is written to disk, but only
 the increment is wrapped in the `setState`:

 6、It is an error to call this method after the framework calls [dispose].
 You can determine whether it is legal to call this method by checking
 whether the [mounted] property is true.


翻译如下:

1、 通知框架此对象的内部状态已更改。

2、 提供的回调将立即同步调用。它不能返回future(回调不能是'async'),因为这样就不清楚实际设置状态的时间。

3、 调用[setState]会通知框架,此对象的更改方式可能会影响此应用程序中的用户界面
子树,使框架为此[状态]安排[构建]对象。

4、 如果直接更改状态而不调用[setState],则框架可能不会为此计划[build]和用户界面
子树可能不会更新以反映新状态。(简单来说,如果不调用setState,就不出触发子Widget的更新)

5、 通常,建议仅在以下情况下使用“设置状态”方法:
包装对状态的实际更改,而不是可能发生的任何计算与更改关联。

例如,[build]使用的值函数递增,然后将更改写入磁盘,但仅限于
增量被包装在“setState”中。

6、 在框架调用[dispose]之后调用此方法是错误的。您可以通过检查来确定调用此方法是否合法
[mounted]属性是否为true

5.2、接下来查看源码:

@protected
  void setState(VoidCallback fn) {
    assert(fn != null);
    assert(() {
      if (_debugLifecycleState == _StateLifecycle.defunct) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() called after dispose(): $this'),
          ErrorDescription(
            'This error happens if you call setState() on a State object for a widget that '
            'no longer appears in the widget tree (e.g., whose parent widget no longer '
            'includes the widget in its build). This error can occur when code calls '
            'setState() from a timer or an animation callback.',
          ),
          ErrorHint(
            'The preferred solution is '
            'to cancel the timer or stop listening to the animation in the dispose() '
            'callback. Another solution is to check the "mounted" property of this '
            'object before calling setState() to ensure the object is still in the '
            'tree.',
          ),
          ErrorHint(
            'This error might indicate a memory leak if setState() is being called '
            'because another object is retaining a reference to this State object '
            'after it has been removed from the tree. To avoid memory leaks, '
            'consider breaking the reference to this object during dispose().',
          ),
        ]);
      }
      if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() called in constructor: $this'),
          ErrorHint(
            'This happens when you call setState() on a State object for a widget that '
            "hasn't been inserted into the widget tree yet. It is not necessary to call "
            'setState() in the constructor, since the state is already assumed to be dirty '
            'when it is initially created.',
          ),
        ]);
      }
      return true;
    }());
    final Object? result = fn() as dynamic;
    assert(() {
      if (result is Future) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() callback argument returned a Future.'),
          ErrorDescription(
            'The setState() method on $this was called with a closure or method that '
            'returned a Future. Maybe it is marked as "async".',
          ),
          ErrorHint(
            'Instead of performing asynchronous work inside a call to setState(), first '
            'execute the work (without updating the widget state), and then synchronously '
           'update the state inside a call to setState().',
          ),
        ]);
      }
      // We ignore other types of return values so that you can do things like:
      //   setState(() => x = 3);
      return true;
    }());
    _element!.markNeedsBuild();
  }

源码代码有点长,我们先把它折起来,大概是这样:

image.png

接下来我们一个个去分析断言部分的代码(断言也可跳过不看)。

5.2.1、断言1分析

就是一个非空判断。

5.2.2、断言2分析

_debugLifecycleState解释:

image.png

_StateLifecycle.defunct解释:

image.png

mounted解释:

image.png

得到以下简化代码

@protected
void setState(VoidCallback fn) {
  assert(fn != null);// 非空判断
  assert(() {
    // 判断"当前是否已经调用过dispose",注释里说了dispose里是不能再调用setState方法的
    if (_debugLifecycleState == _StateLifecycle.defunct) {
        throw FlutterError.fromParts(...);
    }
    
    // 判断"当前对象是否已经被创建且mounted为true"
    //
    // 根据"bool get mounted => _element != null;"定义,可以得知mounted用于判断,当前状态是否还在树上,
    // 因为页面释放的时候,无法再渲染页面,调用setState会报错
    if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
      throw FlutterError.fromParts(...);
    }
    return true;
  }());
  final Object? result = fn() as dynamic;
  assert(...);
  _element!.markNeedsBuild();
}
5.2.3、断言3分析
@protected
  void setState(VoidCallback fn) {
    assert(fn != null);// 非空判断
    assert(...)
    final Object? result = fn() as dynamic;
    assert(() {
      // 判断回调函数fn是否为Future,因为注释里说了"提供的回调将【立即同步】调用。它不能返回future"
      // 如果回调函数是Future会抛出异常
      if (result is Future) {
	throw FlutterError.fromParts(...)
	}
    }());
    _element!.markNeedsBuild();
  }
5.2.4、_element!.markNeedsBuild()分析

我们看到,setState源码方法最后一行会调用

_element!.markNeedsBuild();

进入_element可以看到这样一段代码:

image.png

我们发现:

  1. _element的类型是StatefulElement
  2. context的返回值是_element
  3. setState方法本质上就是调用了_element!.markNeedsBuild()

故而,我们可以猜想:

  • 【setState】是不是可以换成context!.markNeedsBuild()

替换代码、运行、点击按钮:

image.png

结果表明:setState方法可以用context!.markNeedsBuild()替代。只不过setState方法多了一些断言判断。

5.2.5、settate是如何调用build方法的(探究中。。。)

这块内容,分析到这里的时候进行不下去

void scheduleFrame() => platformDispatcher.scheduleFrame();
/// Requests that, at the next appropriate opportunity, the [onBeginFrame] and
/// [onDrawFrame] callbacks be invoked.
///
/// See also:
///
///  * [SchedulerBinding], the Flutter framework class which manages the
///    scheduling of frames.
void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';

所以这块内容后面再补上!!