Flutter源码分析(二)进入StatefulWidget世界

1,447 阅读4分钟
        上一篇分析了StatelessWidget的源码,说道StatelessWidget肯定会联想到StatefulWidget。StatefulWidget是有状态的,那么这个状态是什么?为什么它可以保留当前状态进入下一帧的UI展示?带着这两个问题让我们进入StatefulWidget的源码世界看看。

        关于StatefulWidget的StatefulElement和StatelessWidget的StatelessElement都是继承ComponentElement这个类。在讲述StatefulWidget的源码的时候因为有很多方法在“Flutter源码分析(一)先从StatelessWidget开始”已经详细讲解了,所以写这篇文章就没必要在重复一遍。强烈建议在阅读当前文章之前先阅读上一篇文章。

Flutter源码分析(一)先从StatelessWidget开始

StatefulWidget

         StatefulWidget是有状态的Widget的抽象类,所有继承它的都是有状态的Widget。那么有状态是什么意思呢?意思是可以将当前的内部状态保存下来。简单可以理解为StatefulWidget 可以将当前的状态从上一帧保存到下一帧。比如通过按钮修改某个Widget的颜色,进行刷新到下一帧显示出来,这个就需要用StatefulWidget来进行实现。我们来看一下StatefulWidget的源码:

abstract class StatefulWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatefulWidget({ Key key }) : super(key: key);

  /// Creates a [StatefulElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatefulElement createElement() => StatefulElement(this);

  /// Creates the mutable state for this widget at a given location in the tree.
  ///
  /// Subclasses should override this method to return a newly created
  /// instance of their associated [State] subclass:
  ///
  /// ```dart
  /// @override
  /// _MyState createState() => _MyState();
  /// ```
  ///
  /// The framework can call this method multiple times over the lifetime of
  /// a [StatefulWidget]. For example, if the widget is inserted into the tree
  /// in multiple locations, the framework will create a separate [State] object
  /// for each location. Similarly, if the widget is removed from the tree and
  /// later inserted into the tree again, the framework will call [createState]
  /// again to create a fresh [State] object, simplifying the lifecycle of
  /// [State] objects.
  @protected
  @factory
  State createState();
}

        可以发现StatelessWidget需要实现build的方法,而StatefulWidget需要我们现实State这个类。通过它的汉语意思“状态”,我们能猜到State这个类是用来保存状态的实现。
        同时可以看到StatelessWidget创建了StatelessElement,而StatefulWidget创建了StatefulElement的,分析StatelessWidget的源码的文章里说过,Widget就是Element的配置项,所以重点还需要看Element这个类。分析StatelessWidget的时候重点分析了StatelessElement这个类,那么分析StatefulWidget同样需要重点看一下StatefulElement这个类了。

State

先来看一下Staste的源码如下:

abstract class State<T extends StatefulWidget> with Diagnosticable {
...
  /// an argument.
  T get widget => _widget;
  T _widget;


  _StateLifecycle _debugLifecycleState = _StateLifecycle.created;




  BuildContext get context => _element;
  StatefulElement _element;


  bool get mounted => _element != null;


  @protected
  @mustCallSuper
  void initState() {
    assert(_debugLifecycleState == _StateLifecycle.created);
  }


  @mustCallSuper
  @protected
  void didUpdateWidget(covariant T oldWidget) { }


  @mustCallSuper
  void reassemble() { }


  @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 dynamic 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();
  }


  @protected
  @mustCallSuper
  void deactivate() { }


  @protected
  @mustCallSuper
  void dispose() {
    assert(_debugLifecycleState == _StateLifecycle.ready);
    assert(() {
      _debugLifecycleState = _StateLifecycle.defunct;
      return true;
    }());
  }


  @protected
  Widget build(BuildContext context);


  @protected
  @mustCallSuper
  void didChangeDependencies() { }

...
}

State类的方法

        为了让同学可以看得更清楚一些,我将State的方法通过截图来显示出来了。下边我们来思考一个问题,State这个类是用来管理状态的,那么它提供的 initState, build, didChangeDependencies, didUpdateWidget, dispose, reassemble这些方法是用来干什么的呢?既然是管理状态当然需要提供一些生命周期的方法啊,这些方法其实就是和StatefulElement的生命周期挂钩的。我们通过下图看明了的看一下State(Element)的生命周期方法的调用:

总结State(Element)的生命周期

  • 在Widget组件初始化初始化创建的时候会依次执行initState, didChangeDependencies,build方法。在组件移除之后会执行dispose方法。

  • 在调用setState方法,会依次执行didUpdateWidget,build方法。

  • reassemble这个方法只有在点击热重载按钮的时候进行调用(仅用来测试时候使用)。

StastefulElement

接下来看一下StatefulElement的方法有哪些:

在上一篇StatelessWidget源码分析看到过StatelessElement这个类总共才实现了build, update两个方法。但在StatefulElement这个类中实现了不止有build,update还有activate,dectivate, mount, unmount等等跟Element的生命周期挂钩的方法。这是为什么呢?我们应该也能够猜到和State进行绑定的。那么先深入一下StatefulElement的源码。

首先会执行widget的createState方法创建State对象,并将State对象保存在当前的StatefulElement中。

 StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    assert(() {
      if (!_state._debugTypesAreRight(widget)) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),
          ErrorDescription(
            'The createState function for ${widget.runtimeType} returned a state '
            'of type ${_state.runtimeType}, which is not a subtype of '
            'State<${widget.runtimeType}>, violating the contract for createState.'
          ),
        ]);
      }
      return true;
    }());
    assert(_state._element == null);
    _state._element = this;
    assert(
      _state._widget == null,
      'The createState function for $widget returned an old or invalid state '
      'instance: ${_state._widget}, which is not null, violating the contract '
      'for createState.',
    );
    _state._widget = widget;
    assert(_state._debugLifecycleState == _StateLifecycle.created);

在执行build的方法的时候,它会执行子类的state.build(this);这个方法,由此我们可以知道再用StatefulWidget写布局的时候需要现在State的build方法里边。

@override Widget build() => _state.build(this);

又重写了_firstBuild, performRebuild等等(不清楚这两个方法的作用可以查看上篇文章,这里就不在重复讲解了)。就是为了将State与Element的生命周期进行绑定。

@override
  void _firstBuild() {
    assert(_state._debugLifecycleState == _StateLifecycle.created);
    try {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
      final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic;
      assert(() {
        if (debugCheckForReturnedFuture is Future) {
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary('${_state.runtimeType}.initState() returned a Future.'),
            ErrorDescription('State.initState() must be a void method without an `async` keyword.'),
            ErrorHint(
              'Rather than awaiting on asynchronous work directly inside of initState, '
              'call a separate method to do this work without awaiting it.'
            ),
          ]);
        }
        return true;
      }());
    } finally {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
    }
    assert(() {
      _state._debugLifecycleState = _StateLifecycle.initialized;
      return true;
    }());
    _state.didChangeDependencies();
    assert(() {
      _state._debugLifecycleState = _StateLifecycle.ready;
      return true;
    }());
    super._firstBuild();
  }

final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic;

  @override
  void performRebuild() {
    if (_didChangeDependencies) {
      _state.didChangeDependencies();
      _didChangeDependencies = false;
    }
    super.performRebuild();
  }

_state.didChangeDependencies();

  @override
  void _firstBuild() {
    assert(_state._debugLifecycleState == _StateLifecycle.created);
    try {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
      final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic;
      assert(() {
        if (debugCheckForReturnedFuture is Future) {
          throw FlutterError.fromParts(<DiagnosticsNode>[
            ErrorSummary('${_state.runtimeType}.initState() returned a Future.'),
            ErrorDescription('State.initState() must be a void method without an `async` keyword.'),
            ErrorHint(
              'Rather than awaiting on asynchronous work directly inside of initState, '
              'call a separate method to do this work without awaiting it.'
            ),
          ]);
        }
        return true;
      }());
    } finally {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
    }
    assert(() {
      _state._debugLifecycleState = _StateLifecycle.initialized;
      return true;
    }());
    _state.didChangeDependencies();
    assert(() {
      _state._debugLifecycleState = _StateLifecycle.ready;
      return true;
    }());
    super._firstBuild();
  }

_state.didChangeDependencies();

  @override
  void reassemble() {
    state.reassemble();
    super.reassemble();
  }

state.reassemble();

这里就不在过多展示其他的生命周期的源码了。总结一下StatefulElement,于StatelessElement相比,多了State的状态和重写了Element的生命周期进行绑定。那么它是如何实现跨帧保持状态呢?有没有注意到其实上边已经提及到了,因为State对象保存在当前的Element里边,而Element在仅仅Widget配置项修改的情况下并不会重新创建。所以当前上一次的状态已经会保存为当前状态。(关于这块逻辑具体可以看updateChild方法)。还有一个问题就是当我们修改完widget的配置项如何刷新UI呢?setState方法出场了。

setState

setState方式是用来由当前Widget和子Widget进行刷新的展示下一帧的UI。那让我们先看一下它的源码:

     @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 dynamic 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();
  }

可以看到该方法内部仅仅调用了两个方法。

1、先执行传入的方法。
2、将element标记为“脏”,调用framework刷新机制等待刷新。

_element.markNeedsBuild();

  void markNeedsBuild() {
    assert(_debugLifecycleState != _ElementLifecycle.defunct);
    if (!_active)
      return;
   ...     
    if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);
  }

至于标记为“脏”,在调用一帧刷新之后会执行到performRebuild这个方法。关于performRebuild方法的详细介绍已经在“Flutter源码分析先从StatelessWidget开始”讲过了,这里就不过多说了。最终它会调用build, updateChild方法重新刷新页面。

总结

到这里我们已经将StatelessWidget和StatefulWidget的源码分析已经到一段落尾声了。阅读源码的好处在于我们可以跟深入的了解当前的框架,了解它的实现机制。通过它的实现机制可以写出性能更好的代码。