Flutter

Flutter源码学习--State

2019-02-28  本文已影响0人  慕北人

写在前面:本人小白一枚,自己阅读源码,有什么错误的地方希望大家多多包容

Flutter源码学习--State

一、setState

首先看看Flutter的State源码中对setState方法的注释是怎么说的:

/// Notify the framework that the internal state of this object has changed.
  ///
  /// Whenever you change the internal state of a [State] object, make the
  /// change in a function that you pass to [setState]:
  ///
  /// ```dart
  /// setState(() { _myState = newValue });
  /// ```
  ///
  /// 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.
  ///
  /// 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.
  ///
  /// 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.
  ///
  /// 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`:
  ///
  /// ```dart
  /// Future<void> _incrementCounter() async {
  ///   setState(() {
  ///     _counter++;
  ///   });
  ///   Directory directory = await getApplicationDocumentsDirectory();
  ///   final String dirName = directory.path;
  ///   await File('$dir/counter.txt').writeAsString('$_counter');
  /// }
  /// ```
  ///
  /// 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.
  @protected
    void setState(VoidCallback fn)  

提取出几条重要的信息:

1. State的状态

在framework.dart中定义了一个枚举类_StateLifecycle用来定义State的生命周期:

framework.dart:

enum _StateLifecycle {
  created,  //State刚被创建,但是还没有"初始化";对应于方法调用流程即StatefulWidget的createState方法执行完毕,而State的initState方法还没有执行。
  initialized,   //State执行完initState了,但是还没有准备好去build;即State的didChangeDependencies还没有被执行
  ready,  //State已经准备好去build了,但是dispose方法还没有被执行
  defunct,   //State的dispose方法已经被执行了,意味着State生命的结束
}  

State的生命周期还是很容易记住的,对应的几个方法也是常用到的

2. setState方法

setState方法很简单,刚开始是几个assert进行校验,这里只列出重要的代码:

@protected
void setState(VoidCallback fn) {
    ....
    final dynamic result = fn() as dynamic;
    assert(() {
      if (result is Future) {
        throw FlutterError(
          ...
        );
      }
      return true;
    }());
    _element.markNeedsBuild();
}  

抽丝剥茧后的setState方法就是这么简单:

2.1 _element的实现是什么类型?如何初始化的?

State中对_element的定义如下:

BuildContext get context => _element;
StatefulElement _element;  

用它作为实际的context,而_element的实际类型是StatefulElement;在State源码的其他地方并未找到初始化_element的代码;推测其初始化的地方应该是StatefulWidget的createState中(State在定义context属性时的注释确实说明了这一点)

2.2 StatefulWidget的createState

framework.dart中StatefulWidget的createState方法是一个空方法,让人有点失落,本来猜测可能是StatefulWidget的子类中去实现的,看了看MaterialApp的createState方法,也没有找到StatefulElement是如何被创建,并且传递给State的。

在StatefulWidget中发现了另外一个方法:createElement() => StatefulElement(this);虽然不知道StatefulElement是如何传递给State的,但是这个方法应该就是StatefulElement的初始化的地方了。

二、markNeedsBuild

上面提到了State中的_element实际类型是StatefulElement,经过查看StatefulElement的该方法来源是其间接父类Element,并且该方法由Element实现,子类并未重写

Element.class

老样子,方法实现中刚开始也是一对assert校验,简化后的代码依然很简单:

void markNeedsBuild() {
    ......
    if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);
  }  

其中的dirty属性看来是起一种标记的作用,看看其在Element中是如何定义的:

/// Returns true if the element has been marked as needing rebuilding.
  bool get dirty => _dirty;
  bool _dirty = true;  

这一下,代码就传递到了owner的scheduleBuildFor(this)

三、scheduleBuildFor

同样,去掉assert校验后的方法体为:

void scheduleBuildFor(Element element) {
    ........
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
  }  

操作就是执行onBuildScheduled()方法,而后将element添加到BuildOwner_dirtyElements中,并将element标记为已经在_dirtyElements中

很想知道BuilderOwneronBuildScheduled()方法是如何实现的,虽然发生的原因是由于_element调用了内部的owner.scheduleBuildFor
(this)
方法,但是我并没有在StatefulElement的构造器中发现初始化owner的代码。

不负众望在widget/binding.dartWidgetsBinding中发现了BuildOwner的初始化语句,以及其onBuildScheduled的来源:

/// The [BuildOwner] in charge of executing the build pipeline for the
  /// widget tree rooted at this binding.
  BuildOwner get buildOwner => _buildOwner;
  final BuildOwner _buildOwner = BuildOwner();

@override
  void initInstances() {
    super.initInstances();
    _instance = this;
    buildOwner.onBuildScheduled = _handleBuildScheduled;   //onBuildScheduled的来源
    ui.window.onLocaleChanged = handleLocaleChanged;
    ui.window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    SystemChannels.system.setMessageHandler(_handleSystemMessage);
  }    
_handleBuildScheduled()
void _handleBuildScheduled() {
    // If we're in the process of building dirty elements, then changes
    // should not trigger a new frame.
    assert(() {
      if (debugBuildingDirtyElements) {
        throw FlutterError(
          'Build scheduled during frame.\n'
          'While the widget tree was being built, laid out, and painted, '
          'a new frame was scheduled to rebuild the widget tree. '
          'This might be because setState() was called from a layout or '
          'paint callback. '
          'If a change is needed to the widget tree, it should be applied '
          'as the tree is being built. Scheduling a change for the subsequent '
          'frame instead results in an interface that lags behind by one frame. '
          'If this was done to make your build dependent on a size measured at '
          'layout time, consider using a LayoutBuilder, CustomSingleChildLayout, '
          'or CustomMultiChildLayout. If, on the other hand, the one frame delay '
          'is the desired effect, for example because this is an '
          'animation, consider scheduling the frame in a post-frame callback '
          'using SchedulerBinding.addPostFrameCallback or '
          'using an AnimationController to trigger the animation.'
        );
      }
      return true;
    }());
    ensureVisualUpdate();
  }
ensureVisualUpdate
void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }  
scheduleFrame
void scheduleFrame() {
    if (_hasScheduledFrame || !_framesEnabled)
      return;
    assert(() {
      if (debugPrintScheduleFrameStacks)
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      return true;
    }());
    ui.window.scheduleFrame();
    _hasScheduledFrame = true;
  }  

可见最终调用的是window.scheduleFrame()进行重新绘制

四、总结

通过源码的学习可以有一些收获:

再思setState

  1. 可以看到在整个过程中,setState方法的作用真的只是作为一个接口,来通知_element启动markNeedsBuild,而setState的参数回调的作用就只是更改State的一些属性,从而等待下一次绘制frame的到来;
  2. 可以看到StatefulWidget的setState启动绘制很复杂,我们平时在使用StatefulWidget的时候一定要抓住重点,在WidgetTree的末尾使用StatefulWidget,这样可以减少重新绘制的范围,从而节约成本。比如:有一个界面,内容只有一个ListView,但是含有加载功能,那么该界面的MatrialApp以及Scaffold都应该是写死的,只留下Scaffold的body属性使用StatefulWidget来实现。
  3. BuildContext:在查看官方文档的时候,经常会提到BuildContext代表了Widget插入的位置,为啥BuildContext可以代表位置呢?而BuildContext的具体类型是一个Element的子类,而Element中不仅定义了Element parent,还定义了一系列访问child或者children的接口;可以说Widget树真的是一个树,而且每个节点都可以访问上级和下级
上一篇 下一篇

猜你喜欢

热点阅读