从setState开始,探索Flutter的视图更新流程

1,879 阅读9分钟

​ 如何刷新Flutter应用的一个界面?被问到这个问题,相信很多人第一个想到的都是setState。没错,setState方法确实可以让Statebuild方法重走,从而达到刷新界面的效果。但是你有没有想过,为什么setState可以触发rebuild呢?rebuild后视图又是如何更新的呢?

​ 带着这些疑问,有目标的开始探索源码吧!

1、setState做了什么

State -> setState

[->flutter/src/widgets/framework.dart]

void setState(VoidCallback fn) {
  assert(fn != null);
  assert(() {
    if (_debugLifecycleState == _StateLifecycle.defunct) {
     ///...
    }
    if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
     ///...
    }
    return true;
  }());
  final dynamic result = fn() as dynamic;
  assert(() {
    if (result is Future) {
     ///...
    return true;
  }());
  _element.markNeedsBuild();
}

​ 点进setState方法我们会发现有一堆assert,最终会调用_element.markNeedsBuild()方法。

StatefulElement -> markNeedsBuild

[->flutter/src/widgets/framework.dart]

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

StatefulElement本身没有重写markNeedsBuild方法,所以我们最终调用的还是Element中的markNeedsBuild方法。可以看到上面也是一堆断言,最终判断当前element是否已被标记为dirty,如果没有则标记为dirty,然后调用owner.scheduleBuildFor(this)方法。

BuildOwner -> scheduleBuildFor

[->flutter/src/widgets/framework.dart]

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

scheduleBuildFor方法首先会去调用onBuildScheduled方法,然后把element放入_dirtyElements中,并且标记为dirty。只是加入脏列表中的话并不会造成什么实际效果,因此主动触发刷新的逻辑应该是在onBuildScheduled方法中。

/// Called on each build pass when the first buildable element is marked
/// dirty.
VoidCallback onBuildScheduled;

onBuildScheduled是一个回调,我们需要追踪传入的地方。

WidgetsBinding -> initInstances

[->flutter/src/widgets/binding.dart]

void initInstances() {
  super.initInstances();
  _instance = this;

  assert(() {
    _debugAddStackFilters();
    return true;
  }());

  // 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);
}

​ 通过追踪我们找到了BuildOwner被创建的地方,也就是WidgetsBinding的初始化方法。onBuildScheduled的调用最终会调用WidgetsBinding_handleBuildScheduled方法。

WidgetsBinding -> _handleBuildScheduled

[->flutter/src/widgets/binding.dart]

void _handleBuildScheduled() {
  // If we're in the process of building dirty elements, then changes
  // should not trigger a new frame.
 	///...
  ensureVisualUpdate();
}

_handleBuildScheduled方法上面也是个很长的断言,最终会调用ensureVisualUpdate方法。

SchedulerBinding -> ensureVisualUpdate

[->flutter/src/scheduler/binding.dart]

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

​ 此方法会判断当前的阶段,如果处于空闲或者下一帧回调状态,则会执行scheduleFrame方法,否则什么也不做。

SchedulerBinding -> scheduleFrame

[->flutter/src/scheduler/binding.dart]

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

​ 此方法首先会调用ensureFrameCallbacksRegistered方法。然后调用window的scheduleFrame方法。

SchedulerBinding -> ensureFrameCallbacksRegistered

void ensureFrameCallbacksRegistered() {
  window.onBeginFrame ??= _handleBeginFrame;
  window.onDrawFrame ??= _handleDrawFrame;
}

​ 此方法会注册window下的onBeginFrameonDrawFrame回调。

Window -> scheduleFrame

[->sky_engine/ui/window.dart]

/// 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';

​ 走到这里,就陷入native层了。通过注释我们可以看到,onBeginFrameonDrawFrame回调将会被调用。

SchedulerBinding -> _handleBeginFrame

[->flutter/src/scheduler/binding.dart]

void _handleBeginFrame(Duration rawTimeStamp) {
  if (_warmUpFrame) {//如果当前帧已被处理 直接return
    assert(!_ignoreNextEngineDrawFrame);
    _ignoreNextEngineDrawFrame = true;//忽略后续的drawFrame
    return;
  }
  handleBeginFrame(rawTimeStamp);
}

​ 如果当前帧还未被处理,则会调用handleBeginFrame方法

SchedulerBinding -> handleBeginFrame

void handleBeginFrame(Duration? rawTimeStamp) {
  Timeline.startSync('Frame', arguments: timelineArgumentsIndicatingLandmarkEvent);
  _firstRawTimeStampInEpoch ??= rawTimeStamp;
  //调整当前帧时间戳
  _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
  if (rawTimeStamp != null)
    _lastRawTimeStamp = rawTimeStamp;
  ///...

  assert(schedulerPhase == SchedulerPhase.idle);//只有当前进度为空闲才可往下走
  _hasScheduledFrame = false;
  try {
    // TRANSIENT FRAME CALLBACKS
    Timeline.startSync('Animate', arguments: timelineArgumentsIndicatingLandmarkEvent);
    _schedulerPhase = SchedulerPhase.transientCallbacks;//更新进度
    final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
    _transientCallbacks = <int, _FrameCallbackEntry>{};
    //回调
    callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
      if (!_removedIds.contains(id))
        _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
    });
    _removedIds.clear();
  } finally {
    _schedulerPhase = SchedulerPhase.midFrameMicrotasks;//更新进度
  }
}

​ 可以看到这个方法主要是用来进行一些调整,然后回调当前的进度。

SchedulerBinding -> _handleDrawFrame

[->flutter/src/scheduler/binding.dart]

void _handleDrawFrame() {
  if (_ignoreNextEngineDrawFrame) {
    _ignoreNextEngineDrawFrame = false;
    return;
  }
  handleDrawFrame();
}

​ 如果该帧没被忽略,则会调用到handleDrawFrame中。

SchedulerBinding -> handleDrawFrame

void handleDrawFrame() {
  assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
  Timeline.finishSync(); // end the "Animate" phase
  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();    //postFrameCallback被调用后会清除
    for (final FrameCallback callback in localPostFrameCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!);
  } finally {
    _schedulerPhase = SchedulerPhase.idle;//更新状态
    Timeline.finishSync(); // end the Frame
   ///...
    _currentFrameTimeStamp = null;
  }
}

​ 可以看到此方法也是用来进行回调的。

​ 至此,onBuildScheduled回调执行完毕。element被加入了_dirtyElements中,setState方法也执行完成了。

小结

​ 看完上面的一大堆的调用,我们可以知道setState本质上是调用了elementmarkNeedsBuild方法。该方法会尝试触发帧的调度,然后把当前element加入到BuildOwner的脏列表中。在脏列表中的数据在一系列调度后,会被更新到屏幕上。

image-20210309144906941

2、如何刷新一个StatelessWidget

​ 这个问题我之前在面试中被问过。当时我并没有太深入阅读Flutter的源码,因此当时我的回答是用外部刷新。现在想想,回答的还是有点肤浅了。

​ 虽然StatelessWidget设计上是无状态的,也没有暴露任何刷新的方法,但是我们想要去刷新它也不是不可以的。上面分析了那么多我们已经知道了,setState刷新一个StatefulWidget的本质是调用elementmarkNeedsBuild方法来触发更新,而StatelessWidget自身也是有Element的。因此我们只要调用了StatelessWidgetElementmarkNeedsBuild方法,就可以刷新一个StatelessWidget

​ 代码验证一下:

class RefreshStateless extends StatelessWidget {
  StatelessElement element;
  String text = '测试';

  @override
  Widget build(BuildContext context) {
    element = context;
    return GestureDetector(
      onTap: () {
        text = '我被刷新啦';
        element.markNeedsBuild();
      },
      child: Container(
        child: Text(text),
      ),
    );
  }
}

​ 在build方法中我们保存了StatelessWidgetBuildContext也就是StatelessElement。最开始我们展示的是测试字样。点击后我们把文字更新为我被刷新啦,然后手动调用markNeedsBuild方法。

​ 一开始运行后显示的使我们设置的初始值:

image-20210309110852221

​ 点击后会刷新成如下:

image-20210309110935316

​ 可以看到刷新成功了。只要我们改变了build方法下的描述内容,StatelessWidget也是可以被刷新的,当然并不推荐这么做。

3、脏列表是如何被更新的

​ 上文我们已经知道了setState的本质是将当前的element加入脏列表,脏列表中的数据在后续会被调度处理。现在我们来追踪一下脏列表,看看它是如何被处理的。

BuildOwner -> buildScope

​ 首先在BuildOwner下我们发现脏列表会在buildScope方法下被处理,接着追踪该方法的调用:

image-20210309123039076

WidgetsBinding -> drawFrame

void drawFrame() {
///...
try {
      if (renderViewElement != null)
        buildOwner.buildScope(renderViewElement);
      super.drawFrame();//call super
      buildOwner.finalizeTree();
    }
  ///...
}

​ 由调用链我们追踪到WidgetsBindingdrawFrame方法下调用了此方法,调用完成后会再调用父类的drawFrame方法。现在需要再看看WidgetsBindingdrawFrame方法是如何被调用的:

image-20210309123401803

​ 可以看到只在一处被调用。

RendererBinding -> _handlePersistentFrameCallback

void _handlePersistentFrameCallback(Duration timeStamp) {
  drawFrame();//调用drawFrame
  _scheduleMouseTrackerUpdate();
}

​ 继续追踪调用:

RendererBinding -> initInstances

void initInstances() {
  super.initInstances();
  _instance = this;
  _pipelineOwner = PipelineOwner(
    onNeedVisualUpdate: ensureVisualUpdate,
    onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
    onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
  );
  window
    ..onMetricsChanged = handleMetricsChanged
    ..onTextScaleFactorChanged = handleTextScaleFactorChanged
    ..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
    ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
    ..onSemanticsAction = _handleSemanticsAction;
  initRenderView();
  _handleSemanticsEnabledChanged();
  assert(renderView != null);
  addPersistentFrameCallback(_handlePersistentFrameCallback);//持久回调
  initMouseTracker();
}

​ 我们可以看到该方法被注册为一个持久的监听,每当有帧更新的时候都会被调用。该回调具体的回调时机我们已经分析过了,在触发了windowshceduleFrame回调后,会被调用,具体回调的实现SchedulerBInding 的 handleDrawFrame方法中。

​ 现在我们可以知道了脏列表更新的方法调用顺序了:

image-20210309155450174

BuildOwner -> buildScope

void buildScope(Element context, [ VoidCallback callback ]) {
  if (callback == null && _dirtyElements.isEmpty)
    return;
  ///... assert部分
  Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent);//开始Build
  try {
    _scheduledFlushDirtyElements = true;
    if (callback != null) {
     	///...
      _dirtyElementsNeedsResorting = false;
      try {
        callback();
      } finally {
        ///... a
      }
    }
    _dirtyElements.sort(Element._sort);//按深度排序 自上而下开始构建
    _dirtyElementsNeedsResorting = false;
    int dirtyCount = _dirtyElements.length;
    int index = 0;
    while (index < dirtyCount) {
      ///... assert
      try {
        _dirtyElements[index].rebuild();//调用 dirty element的rebuild方法
      } catch (e, stack) {
        /// ...error handle
      }
      index += 1;
      if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
        //build之后可能会有新的脏列表数据 在此进行处理
        _dirtyElements.sort(Element._sort);
        _dirtyElementsNeedsResorting = false;
        dirtyCount = _dirtyElements.length;//调整dirtyCount
        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;
    Timeline.finishSync();
   ///...
  }
  assert(_debugStateLockLevel >= 0);
}

​ buildScope方法会将_dirtyElements列表数据重新排序,然后自上而下调用rebuild方法。

Element -> rebuild

void rebuild() {
  ///... assert 
  performRebuild();//实际执行build的地方
 ///... assert
}

rebuild方法里只是一堆判断和断言,最后把处理逻辑交给了performRebuild方法。不同的Element有不同的处理逻辑。这里看一下StatefulElement的实现。

image-20210309171312116

StatefulElement -> performRebuild

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

​ 如果依赖发生了改变,会回调state的didChangeDependencies,接着调用了父类的performRebuild方法。

ComponentElement -> performRebuild

void performRebuild() {
  if (!kReleaseMode && debugProfileBuildsEnabled)
    Timeline.startSync('${widget.runtimeType}',  arguments: timelineArgumentsIndicatingLandmarkEvent);

  assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
  Widget built;
  try {
    assert(() {
      _debugDoingBuild = true;
      return true;
    }());
    built = build();//调用build方法
    assert(() {
      _debugDoingBuild = false;
      return true;
    }());
    debugWidgetBuilderValue(widget, built);
  } catch (e, stack) {
    ///...
  } finally {
    // We delay marking the element as clean until after calling build() so
    // that attempts to markNeedsBuild() during build() will be ignored.
    _dirty = false;
    assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
  }
  try {
    _child = updateChild(_child, built, slot);//更新element
    assert(_child != null);
  } catch (e, stack) {
   ///...
  if (!kReleaseMode && debugProfileBuildsEnabled)
    Timeline.finishSync();
}

​ 这里我们可以看到build方法被调用,生成了新的Widget配置,接着通过updateChild方法来更新element。updateChild方法的实现继承自Element

Element -> updateChild

Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child);
    return null;
  }
  Element newChild;
  if (child != null) {
    bool hasSameSuperclass = true;
    assert(() {
      final int oldElementClass = Element._debugConcreteSubtype(child);
      final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
      ///hot reload情况下可能会发生element类型改变
      hasSameSuperclass = oldElementClass == newWidgetClass;
      return true;
    }());
    if (hasSameSuperclass && child.widget == newWidget) {
      //element类型一致 配置一致
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      newChild = child;
    } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
       //element类型一致 配置类型和key一致
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      child.update(newWidget);//更新widget
      assert(child.widget == newWidget);
      assert(() {
        child.owner._debugElementWasRebuilt(child);
        return true;
      }());
      newChild = child;
    } else {
      deactivateChild(child);
      assert(child._parent == null);
      //创建新的element
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
     //创建新的element
    newChild = inflateWidget(newWidget, newSlot);
  }
	///...
  return newChild;
}

​ 这段代码的逻辑并不复杂。如果newWidget为null,则element的配置没有了,这时候需要把这个element从树中移除。如果之前的child为空,则加载newWidget的配置,生成一个element返回。否则会判断child能否更新。只有不能更新的情况下才会创建一个新的Element。

StatefulElement -> update

void update(StatefulWidget newWidget) {
  super.update(newWidget);
  assert(widget == newWidget);
  final StatefulWidget oldWidget = _state._widget;
  _dirty = true;
  _state._widget = widget as StatefulWidget;
  try {
    _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
    final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;//回调didUpdateWidget
   ///...
  } finally {
    _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
  }
  rebuild();//rebuild
}

​ StatefulElement在update方法中回调了didUpdateWidget生命周期并马上rebuild。

RenderBinding -> drawFrame

void drawFrame() {
  assert(renderView != null);
  pipelineOwner.flushLayout();
  pipelineOwner.flushCompositingBits();
  pipelineOwner.flushPaint();
  if (sendFramesToEngine) {
    renderView.compositeFrame(); //实际渲染的地方
    pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
    _firstFrameSent = true;
  }
}

​ 终于走到这里,这也是实际构建视图的地方。pipelineOwner会进行布局、混合、绘制等一系列的操作。最后通过RenderViewcompositeFrame实际的去进行渲染一帧。

BuildOwner -> finalizeTree

void finalizeTree() {
  Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent);
  try {
    lockState(() {
      //取消挂载不活动的element
      _inactiveElements._unmountAll(); // this unregisters the GlobalKeys
    });
    ///...
  } catch (e, stack) {
    ///...
  } finally {
    Timeline.finishSync();
  }
}

​ 这个方法看起来很长,实际在非debug模式下只做了一件事。那就是释放不活动的element。

4、总结

​ 不想再写了,就把图补全一下吧。这里先把setState的流程图再补完一下:

image-20210309203213963

再来看脏列表刷新相关调用:

image-20210309211751151