Flutter setState流程分析

5,151 阅读2分钟

介绍

Flutter中最常用的Widget是StatelessWidgetStatefulWidget,分别对应于无状态的组件和有状态的组件。而StatefulWidget中更新状态的方法就是setState(fn),调用该方法后,会重新调用StatefulWidgetbuild方法重新构建组件,达到刷新界面的效果。那么调用setState方法后,是通过什么的样流程走到build方法的呢?带着这个疑惑通过阅读源码来分析StatefulWidget的更新流程。

源码解析

setState方法有一个fn参数,一般会在该函数中执行更新状态的操作,在方法体内会首先同步执行fn函数。这个函数的返回值不能是Future类型,即不能是async异步函数。执行完fn函数后,调用_elementmarkNeedsBuild方法。

void setState(VoidCallback fn) {
    ...
    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().'
          ),
        ]);
      }
      return true;
    }());
    _element.markNeedsBuild();
  }

StatefulWidget对应的Element是StatefulElement,在StatefulElement中的构造方法中会通过StatefulWidgetcreateState创建State,同时将element本身设置给State_element属性。而State也被保存在Element_state属性中。

  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),
        super(widget) {
    ...
    _state._element = this;
    ...
    _state._widget = widget;
    assert(_state._debugLifecycleState == _StateLifecycle.created);
  }

Element markNeedsBuild

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

markNeedsBuild方法会调用ownerscheduleBuildFor方法,将该element标记为dirty,并且将element加入到一个全局的表示需要更新的Element列表中。ownerBuildOwner对象。

BuildOwner scheduleBuildFor

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

这个方法主要执行几个任务

  1. 判断element是否已经加入到_dirtyElements列表中,如果已经在列表中,就直接返回,不用再执行下面的操作。
  2. 判断_scheduledFlushDirtyElements是否为false,这个变量表示当前是否正在rebuild_dirtyElements中的元素。如果没有正在rebuild,并且onBuildScheduled回调不为空,就调用onBuildScheduled函数。
  3. 将element加入到_dirtyElements中,并且标记element的_inDirtyListtrue,表示已经加入到脏元素列表。

通过搜索可以查到,BuildOwner是在WdigetBindinginitInstances方法中创建的,并且创建完成后设置了onBuildScheduled回调为WidgetsBinding的_handleBuildScheduled方法。所以scheduleBuildFor方法又会调用到WidgetsBinding_handleBuildScheduled方法。

WdigetBinding _handleBuildScheduled

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    ...
    // Initialization of [_buildOwner] has to be done after
    // [super.initInstances] is called, as it requires [ServicesBinding] to
    // properly setup the [defaultBinaryMessenger] instance.
    _buildOwner = BuildOwner();
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
  }
void _handleBuildScheduled() {
    // If we're in the process of building dirty elements, then changes
    // should not trigger a new frame.
    ...
    ensureVisualUpdate();
  }

_handleBuildScheduled调用ensureVisualUpdate注意ensureVisualUpdate并不是WidgetsBinding中的方法,而是SchedulerBinding中的方法,WidgetsBindingSchedulerBinding都是mixin,被集成在WidgetsFlutterBinding类中,在应用启动执行runApp函数时会进行初始化。在dart中,一个类同时引入多个mixin,根据with的顺序,最右边的优先级更高。mixin有个线性化处理,如果右边的mixin重写了某一方法,并且在重写方法中调用了super.overrideMethod(),就会调用其左边的mixin的相应方法。

'Dart中的Mixins通过创建一个新类来实现,该类将mixin的实现层叠在一个超类之上以创建一个新类 ,它不是“在超类中”,而是在超类的“顶部”,因此如何解决查找问题不会产生歧义。
— Lasse R. H. Nielsen on StackOverflow.'

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

SchedulerBinding scheduleFrame

void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }

ensureVisualUpdate方法会通过SchedulerPhase枚举类判断当前的刷新状态。一共有五种状态 状态的转变流程为 transientCallbacks -> midFrameMicrotasks -> persistentCallbacks -> postFrameCallbacks -> idle 通过后面的分析,可以知道真正的刷新过程是在persistentCallbacks状态完成的。 所以,如果上次刷新已经完成(postFrameCallbacksidle状态),就会调用scheduleFrame请求再次刷新。

void scheduleFrame() {
    if (_hasScheduledFrame || !framesEnabled)
      return;
    ...
    ensureFrameCallbacksRegistered();
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }

WidgetBindingscheduleFrame会首先调用ensureFrameCallbacksRegistered方法确保window的回调函数以被注册。再调用windowscheduleFrame的方法。

void ensureFrameCallbacksRegistered() {
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
}
/// Requests that, at the next appropriate opportunity, the [onBeginFrame]
/// and [onDrawFrame] callbacks be invoked.
void scheduleFrame() native 'Window_scheduleFrame';

WindowscheduleFrame方法是个native方法,通过上面的注释,可以知道调用该方法后,onBeginFrame回调和onDrawFrame回被调用。这两个回调已经通过ensureFrameCallbacksRegistered设置为WidgetBinding_handleBeginFrame_handleDrawFrame方法。我们重点看下_handleDrawFrame方法。

void _handleDrawFrame() {
    if (_ignoreNextEngineDrawFrame) {
      _ignoreNextEngineDrawFrame = false;
      return;
    }
    handleDrawFrame();
  }
/// Called by the engine to produce a new frame.
///
/// This method is called immediately after [handleBeginFrame]. It calls all
/// the callbacks registered by [addPersistentFrameCallback], which typically
/// drive the rendering pipeline, and then calls the callbacks registered by
/// [addPostFrameCallback].
void handleDrawFrame() {
    ...
    try {
      // PERSISTENT FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
      for (final FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

      // POST-FRAME CALLBACKS
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
      final List<FrameCallback> localPostFrameCallbacks =
          List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (final FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);
    } finally {
      _schedulerPhase = SchedulerPhase.idle;
      ...
      _currentFrameTimeStamp = null;
    }
  }

handleDrawFrame方法上面的注释已经说了该方法的作用,被引擎调用创建一个新的帧。这个方法流程也比较清晰,首先会循环执行_persistentCallbacks中的callback,这里的callback可以通过WidgetsBinding.instance.addPersistentFrameCallback(fn)注册;然后,再复制一份_postFrameCallbacks的拷贝,并将原_postFrameCallbacks列表清空,_postFrameCallbacks中保存重绘后执行的回调函数,并且只执行一次,可以通过WidgetsBinding.instance.addPostFrameCallback(fn)添加回调。执行完_persistentCallbacks_postFrameCallbacks后,便将状态设置为SchedulerPhase.idle表示已经刷新过。

通过注释可以知道是通过addPersistentFrameCallback来驱动渲染的。通过搜索,可以看到在RendererBindinginitInstances方法中注册了persistentFrameCallback回调。

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    ...
    initRenderView();
    _handleSemanticsEnabledChanged();
    assert(renderView != null);
    addPersistentFrameCallback(_handlePersistentFrameCallback);
    initMouseTracker();
  }
}

_handlePersistentFrameCallback回调函数中直接调用了drawFrame方法。

void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
    _mouseTracker.schedulePostFrameCheck();
  }
@protected
void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
        renderView.compositeFrame(); // this sends the bits to the GPU
        pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
        _firstFrameSent = true;
    }
}

需要注意的是,WidgetsBinding也实现了drawFrame,并且WidgetsBinding在被mixin到WidgetsFlutterBinding类时是在最右边,所以它的方法优先级最高。_handlePersistentFrameCallback中调用drawFrame方法时,会先调用WidgetsBinding中的drawFrame方法。

WidgetsBinding drawFrame

@override
  void drawFrame() {
    ...  
    try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();
      buildOwner.finalizeTree();
    } finally {
      assert(() {
        debugBuildingDirtyElements = false;
        return true;
      }());
    }
    ...
  }

WidgetsBindingdrawFrame方法中,先调用了buildOwnerbuildScope方法,然后再调用了super.drawFrame(),通过super.drawFrame()可以调用到RendererBindingdrawFrame方法。先看buildOwnerbuildScope方法。

BuildOwner buildScope

void buildScope(Element context, [ VoidCallback callback ]) {
    if (callback == null && _dirtyElements.isEmpty)
      return;
    ...
    try {
      _dirtyElements.sort(Element._sort);
      _dirtyElementsNeedsResorting = false;
      int dirtyCount = _dirtyElements.length;
      int index = 0;
      while (index < dirtyCount) {
        ...
        try {
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
          ...
        }
        index += 1;
        if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
          _dirtyElements.sort(Element._sort);
          _dirtyElementsNeedsResorting = false;
          dirtyCount = _dirtyElements.length;
          while (index > 0 && _dirtyElements[index - 1].dirty) {
            // It is possible for previously dirty but inactive widgets to move right in the list.
            // We therefore have to move the index left in the list to account for this.
            // We don't know how many could have moved. However, we do know that the only possible
            // change to the list is that nodes that were previously to the left of the index have
            // now moved to be to the right of the right-most cleaned node, and we do know that
            // all the clean nodes were to the left of the index. So we move the index left
            // until just after the right-most clean node.
            index -= 1;
          }
        }
      }
      ...
    } finally {
      for (final Element element in _dirtyElements) {
        assert(element._inDirtyList);
        element._inDirtyList = false;
      }
      _dirtyElements.clear();
      _scheduledFlushDirtyElements = false;
      _dirtyElementsNeedsResorting = null;
      ...
    }
  }

buildScope的核心逻辑就是,首先对_dirtyElements按照深度进行排序,再遍历_dirtyElements列表,调用其中元素的rebuild方法。rebuild方法定义在Element类中。

void rebuild() {
    ...
    performRebuild();
    ...
  }
@protected
  void performRebuild();

performRebuildElement类中的抽象方法,各个子类会实现该方法。StateElement的父类是ComponentElement,先看ComponentElementperformRebuild方法

@override
  void performRebuild() {
    ...
    Widget built;
    try {
      ..
      built = build();
      ..
    } catch (e, stack) {
      _debugDoingBuild = false;
      built = ErrorWidget.builder(
        _debugReportException(
          ErrorDescription('building $this'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(this));
          },
        ),
      );
    } finally {
      ...
    }
    try {
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      ...
      _child = updateChild(null, built, slot);
    }
    ...
  }

在这个方法中,直接调用build方法创建Widget,如果build方法产生异常,就会创建一个ErrorWidget,就是经常看到的红色警告界面。调用完build方法后,会再调用updateChild(_child, built, slot)更新子Widget。 StatelessElementStatefulElement重写了build方法,分别调用了WidgetStatebuild方法。

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

前面提到WidgetsBindingdrawFrame方法会通过super.drawFrame()调用到RendererBindingdrawFrame方法,再回头看RendererBindingdrawFrame方法。

@protected
  void drawFrame() {
    assert(renderView != null);
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    if (sendFramesToEngine) {
      renderView.compositeFrame(); // this sends the bits to the GPU
      pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
      _firstFrameSent = true;
    }
  }

RendererBindingdrawFrame方法,通过pipelineOwner对象重新layout和paint,已达到更新UI的效果。

总结

StatefulWidget通过setState方法将其对应的StatefulElement添加到BuildOwnerdirtyElements中,并触发一次刷新。在收到刷新回调后,遍历dirtyElements中的元素,执行rebuild操作,以更新显示状态。