Flutter 从源码理解setState刷新机制

3,017 阅读3分钟

Flutter 中 setState()方法究竟干了些什么,StatefulWidget 是如何刷新界面的?

setState 更新流程

在 framework.dart类中,我们可以看到setState的源码:

setState
  /// 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) {
     ...//过滤一些不满足条件的调用
    _element.markNeedsBuild();
  }

当调用过 dispose()方法之后再调用 setState()方法会出错,可以通过[mounted]属性是否为 true 来判断调用此方法是否合法。

接着在看Element类中的markNeedsBuild方法:

markNeedsBuild
  /// Marks the element as dirty and adds it to the global list of widgets to
  /// rebuild in the next frame.
  ///
  /// Since it is inefficient to build an element twice in one frame,
  /// applications and widgets should be structured so as to only mark
  /// widgets dirty during event handlers before the frame begins, not during
  /// the build itself.
  void markNeedsBuild() {
    assert(_debugLifecycleState != _ElementLifecycle.defunct);
    if (!_active)
      return;//返回
     ...
    if (dirty)
      return;
    _dirty = true;
    //调用scheduleBuildFor方法
    owner.scheduleBuildFor(this);
  }

将 element 元素标记为“脏”,并把它添加到全局的“脏”链表里,以便在下一帧更新信号到来时更新,这里的“ 脏”链表是待更新的链表,更新过后就不“脏”了。在_active=false 的时候直接返回。

BuildOwner 类

BuildOwner 就是 widget framework 的管理类,它跟踪哪些 widget 需要重新构建。

scheduleBuildFor
  /// Adds an element to the dirty elements list so that it will be rebuilt
  /// when [WidgetsBinding.drawFrame] calls [buildScope].
  void scheduleBuildFor(Element element) {
     ...
    if (element._inDirtyList) {
      ...
      _dirtyElementsNeedsResorting = true;
      return;
    }
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled();//回调
    }
    _dirtyElements.add(element);//把element加入脏元素链表
    element._inDirtyList = true;
    assert(() {
      if (debugPrintScheduleBuildForStacks)
        debugPrint('...dirty list is now: $_dirtyElements');
      return true;
    }());
  }

把一个 element 添加到 _dirtyElements 链表,以便当[WidgetsBinding.drawFrame]中调用 buildScope 的时候能够重构 element。onBuildScheduled()是一个 BuildOwner 的回调,我们接着看这个回调在哪被初始化就行了,看到注释中提到 WidgetsBinding 类, 我忽然想到之前 从源码了解 Widget、Element、RenderObject中记录,当 Flutter 调用 runApp()中:

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}

WidgetsFlutterBinding 中就混入了很多 Binding。

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  //返回WidgetsBinding实例
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

其中看到了 WidgetsBinding的身影。

我们在binding.dart文件中查看WidgetsBinding的初始化方法:

在 initInstances()初始化方法中:

/// The glue between the widgets layer and the Flutter engine.
mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
    //初始化的时候把_handleBuildScheduled
    //赋值给buildOwner.onBuildScheduled,这就和上面的回调接口
    //对上了
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    window.onLocaleChanged = handleLocaleChanged;
    window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    SystemChannels.system.setMessageHandler(_handleSystemMessage);
    FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
  }
  ...

  void _handleBuildScheduled() {
    //调用ensureVisualUpdate
    ensureVisualUpdate();
  }
}

可见 _handleBuildScheduled 调用了SchedulerBinding 类中的ensureVisualUpdate。

ensureVisualUpdate
  void ensureVisualUpdate() {
    switch (schedulerPhase) {
      case SchedulerPhase.idle:
      case SchedulerPhase.postFrameCallbacks:
        //当schedulerPhase为SchedulerPhase.idle,
        //SchedulerPhase.postFrameCallbacks时调用
        //scheduleFrame()
        scheduleFrame();
        return;
      case SchedulerPhase.transientCallbacks:
      case SchedulerPhase.midFrameMicrotasks:
      case SchedulerPhase.persistentCallbacks:
        return;
    }
  }

关于 SchedulerPhase 的 5 个枚举值:

状态 含义
idle 没有正在处理的帧,可能正在执行的是 WidgetsBinding.scheduleTask,scheduleMicrotask,Timer,事件 handlers,或者其他回调等
transientCallbacks SchedulerBinding.handleBeginFrame 过程, 处理动画状态更新
midFrameMicrotasks 处理 transientCallbacks 阶段触发的微任务(Microtasks)
persistentCallbacks WidgetsBinding.drawFrame 和 SchedulerBinding.handleDrawFrame 过程,build/layout/paint 流水线工作
postFrameCallbacks 主要是清理和计划执行下一帧的工作

ensureVisualUpdate()中调用了 scheduleFrame()方法。

  void scheduleFrame() {
    if (_hasScheduledFrame || !_framesEnabled)
      return;
    assert(() {
      if (debugPrintScheduleFrameStacks)
        debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
      return true;
    }());
    //调用Window 的scheduleFrame方法是一个 native 方法
    window.scheduleFrame();
    _hasScheduledFrame = true;
  }

native 层的带代码我就无能为力了,不过从Flutter 从启动到显示中了解到: WidgetsFlutterBinding 混入的这些 Binding 中基本都是监听并处理 Window 对象的一些事件,然后将这些事件按照 Framework 的模型包装、抽象然后分发。可以看到 WidgetsFlutterBinding 正是粘连 Flutter engine 与上层 Framework 的“胶水”。

  • GestureBinding:提供了 window.onPointerDataPacket 回调,绑定 Framework 手势子系统,是 Framework 事件模型与底层事件的绑定入口。

  • ServicesBinding:提供了 window.onPlatformMessage 回调, 用于绑定平台消息通道(message channel),主要处理原生和 Flutter 通信。

  • SchedulerBinding:提供了 window.onBeginFrame 和 window.onDrawFrame 回调,监听刷新事件,绑定 Framework 绘制调度子系统。

  • PaintingBinding:绑定绘制库,主要用于处理图片缓存。

  • SemanticsBinding:语义化层与 Flutter engine 的桥梁,主要是辅助功能的底层支持。

  • RendererBinding: 提供了 window.onMetricsChanged 、window.onTextScaleFactorChanged 等回调。它是渲染树与 Flutter engine 的桥梁。

  • WidgetsBinding:提供了 window.onLocaleChanged、onBuildScheduled 等回调。它是 Flutter widget 层与 engine 的桥梁。

之前的文中有说过,UI 的绘制逻辑是在 Render 树中实现的,所以这里还来细看 RendererBinding 的逻辑。

RendererBinding
void initInstances() {
  ...

  //监听Window对象的事件
  ui.window
    ..onMetricsChanged = handleMetricsChanged
    ..onTextScaleFactorChanged = handleTextScaleFactorChanged
    ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
    ..onSemanticsAction = _handleSemanticsAction;

  //添加PersistentFrameCallback
  addPersistentFrameCallback(_handlePersistentFrameCallback);
}

addPersistentFrameCallback 中添加 _handlePersistentFrameCallback 最终调用了 drawFrame 而 WidgetsBinding 重写了 RendererBinding 中的 drawFrame() 方法。最终发现我们又回到了 WidgetsBinding 这个类中,在 WidgetsBinding 中 drawFrame 的实现如下:

@override
void drawFrame() {
 ...
  try {
    if (renderViewElement != null)
      // 重构需要更新的element
      buildOwner.buildScope(renderViewElement);
    super.drawFrame(); //调用RendererBinding的drawFrame()方法
    buildOwner.finalizeTree();
  }
}

在上面 scheduleBuildFor 方法介绍中有提到:"scheduleBuildFor 是把一个 element 添加到 _dirtyElements 链表,以便当[WidgetsBinding.drawFrame]中调用 buildScope 的时候能够重构 element。onBuildScheduled()是一个 BuildOwner 的回调"。在 drawFrame 中调用 buildOwner.buildScope(renderViewElement)更新 elements。

  void buildScope(Element context, [ VoidCallback callback ]) {
    ...

      while (index < dirtyCount) {
        assert(_dirtyElements[index] != null);
        assert(_dirtyElements[index]._inDirtyList);
        assert(!_dirtyElements[index]._active || _dirtyElements[index]._debugIsInScope(context));
        try {
          //while 循环进行元素重构
          _dirtyElements[index].rebuild();
        } catch (e, stack) {
        ...
        }
      }

  }

小结

setState 调用时会自己添加到 BuildOwner 的_dirtyElements 脏链表中,然后调用 window.scheduleFrame()来注册Vsync回调。当下一次vsync信号的到来时会执行handleBeginFrame()和handleDrawFrame()来更新UI。

这就是整个setState的更新流程。