持续创作,加快生长!这是我参与「日新方案 6 月更文挑战」的第2天,点击查看活动详情

前面咱们介绍了 GestureBinding,知道了 Flutter手势处理流程,这一篇咱们就从 SchedulerBinding 的代码中看帧调度进程。

往期精彩:

Flutter 必知必会系列 —— runApp 做了啥

Flutter 必知必会系列 —— mixin 和 BindingBase 的奇妙配合

Flutter 必知必会系列 —— 从 GestureBinding 中看 Flutter 手势处理进程

Flutter 帧的阶段

Flutter 把一帧分为了 6 个阶段,每个阶段做不同的作业,而且把帧阶段包装在枚举类 SchedulerPhase 中,作为前期预备,咱们认识一下这个类和这几个阶段是啥。

idle —— 空闲

这个阶段没有帧使命履行,在这个阶段履行的代码是:SchedulerBinding.scheduleTask 发布的 TaskCallbackscheduleMicrotask 注册的微使命、Timer 声明的使命、用户的手势处理器、FutureStream 的使命。

这里我们需求先看一下 Dart 的使命履行次序,这个链接需求翻墙哦~,了解一下 Dart 是怎么处理事件行列、微使命行列的,以便咱们写出更好的异步代码。

transientCallbacks —— 瞬时使命阶段

这个阶段履行 SchedulerBinding.scheduleFrameCallback 增加的回调,一般情况下,这些回调履行动画有关的核算,咱们在前面的动画介绍中,讲过这个当地, Flutter 动画是这么动起来的

midFrameMicrotasks —— 帧中微使命处理阶段

transientCallbacks 也会产生一些微使命,这些微使命会在这个阶段履行。

persistentCallbacks —— 持续使命处理阶段

这个阶段主要处理 SchedulerBinding.addPersistentFrameCallback 增加的回调,与 transientCallbacks 阶段相对应,这个阶段处理 build/layout/paint

postFrameCallbacks —— 帧完毕阶段

这个阶段履行 SchedulerBinding.addPostFrameCallback 增加的回调,一般情况下会做一些帧整理作业和建议下一帧。

比如咱们举个例子:

void update(TextEditingValue newValue) {
  if (_value == newValue)
    return;
  _value = newValue;
  if (SchedulerBinding.instance!.schedulerPhase == SchedulerPhase.persistentCallbacks) { //榜首处
    SchedulerBinding.instance!.addPostFrameCallback(_markNeedsBuild);
  } else {
    _markNeedsBuild();
  }
}

这一段代码是 EditText 的代码,在更新显现字段的时分,看榜首处的显现逻辑。假如当前是制作阶段,那就在本帧的完毕增加一个建议从头 build 的使命,否则就直接建议从头 build 使命。

小结

帧的调度分为六个阶段,代码体现在 SchedulerPhase 中,帧和行列的履行和关联如下:

Flutter 必知必会系列 —— 从 SchedulerBinding 中看 Flutter 帧调度

Flutter 建议帧调度

帧调度的办法

Flutter 中建议帧调度的办法就下面几个:

办法名 效果
scheduleWarmUpFrame 调度一帧,而且该帧尽可能快的履行,不需求等候引擎的 “Vsync” 信号
scheduleFrame 调度一帧
scheduleForcedFrame 强行调度一帧,即使是在熄屏的情况下也会履行
scheduleFrameCallback 调度一帧,而且给这一帧设置一个履行回调,回调会在 transient 阶段履行

这几个办法大差不差,咱们以中心的 scheduleFrame 的为例,看看干了啥事。

void scheduleFrame() {
  if (_hasScheduledFrame || !framesEnabled)
    return;
  ensureFrameCallbacksRegistered();//榜首处
  window.scheduleFrame();
  _hasScheduledFrame = true;
}
void ensureFrameCallbacksRegistered() {
  window.onBeginFrame ??= _handleBeginFrame;
  window.onDrawFrame ??= _handleDrawFrame;
}

就干了两件事:榜首:确保 windowonBeginFrameonDrawFrame 回调现已设置了。 第二:调用 window 的建议帧流程。

之前咱们提到过,Flutter 和 Native 的交互都是经过回调的办法,window 的建议流程终究会调用到

void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';

这是一个 native 办法,相当于 Flutter 告知原生,原生请开端制作吧,我现已预备好了。然后与原生在收到 “Vsync” 信号之后,会调用到榜首处注册的两个回调 onBeginFrame 和 onDrawFrame。

咱们看:

@pragma('vm:entry-point')
void _beginFrame(int microseconds, int frameNumber) {
  PlatformDispatcher.instance._beginFrame(microseconds);
  PlatformDispatcher.instance._updateFrameData(frameNumber);
}
@pragma('vm:entry-point')
void _drawFrame() {
  PlatformDispatcher.instance._drawFrame();
}
-----------------------------------------------------------------------------
void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';

那么 windowonBeginFrameonDrawFrame 便是帧调度履行的内容了。

onBeginFrame 开端呼应

咱们来看 Flutter 是怎么呼应的。

/// ...代码省略
void handleBeginFrame(Duration? rawTimeStamp) {
  _hasScheduledFrame = false;
  try {
    _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; // 第三处
  }
}

榜首处和第三处是修正调度的阶段,先设置为 transientCallbacks 阶段,而且在这个阶段,履行了 _transientCallbacks 中的回调。

_transientCallbacks 中的回调是啥呢? 便是 scheduleFrameCallback 办法参数中增加的。

int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
  scheduleFrame();
  _nextFrameCallbackId += 1;
  _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
  return _nextFrameCallbackId;
}

\_transientCallbacks 是一个 Mapkey 是帧的 idvalue 是这一帧在 transient 阶段应该履行的回调数组。 在运用 scheduleFrameCallback 办法建议帧使命的时分,需求传递一个 callback 回调,这个回调便是建议帧的下一帧的 transient 阶段履行。

那么谁调用了 scheduleFrameCallback 这个办法呢? 便是动画!所以动画的核算先与布局制作等。具体能够看这里 Flutter 动画是这么动起来的

不管回调会不会反常,都会履行到第三处,第三处便是帧调度进入了 midFrameMicrotasks 阶段。

上面便是 \_handleBeginFrame,会履行本帧的 \_transientCallbacks 回调,由于动画建议的时分会设置这个回调,所以基本便是动画的核算。

由于动画会是 Future 等的核算,所以在 midFrameMicrotasks 阶段,这些异步的核算依然会履行。 下面咱们看 _handleDrawFrame 的履行。

onDrawFrame 制作使命

/// 代码省略
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;
  }
}

上面的代码还是比较明晰的,便是 修正状况、履行回调。咱们仔细看。

首先看榜首处的代码,便是从 midFrameMicrotasks 阶段进入到 persistentCallbacks。 然后履行 _persistentCallbacks 回调,_persistentCallbacks 回调是谁呢?

_persistentCallbacks 是经过 addPersistentFrameCallback 增加的。

void addPersistentFrameCallback(FrameCallback callback) {
  _persistentCallbacks.add(callback);
}

那么谁调用了这个办法呢?便是 RendererBinding 的初始化中。

/// 代码省略
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
  @override
  void initInstances() {
    super.initInstances();
    addPersistentFrameCallback(_handlePersistentFrameCallback);//榜首处
  }
  void _handlePersistentFrameCallback(Duration timeStamp) {
    drawFrame();
  }
  void drawFrame() {
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
  }
}

便是榜首处的代码, RendererBinding 初始化时,增加了大局的帧 persistent 回调。 回调的使命便是布局(Layout)、组成(CompositingBit)、制作(Paint)

所以说,在 persistentCallbacks 阶段,履行的使命便是布局、组成、制作。

咱们持续看,persistentCallbacks 的回调履行完了之后,就会到 postFrameCallbacks 阶段,履行 _postFrameCallbacks 回调。postFrameCallbacks 阶段是帧的结尾阶段,我们能够运用这个办法来做一些收尾的作业。比如获取尺度等等。和 persistentCallbacks 不同,_postFrameCallbacks 是一次性的。

void handleDrawFrame() {
  try {
    // PERSISTENT FRAME CALLBACKS
    _schedulerPhase = SchedulerPhase.persistentCallbacks;
    for (final FrameCallback callback in _persistentCallbacks)
      _invokeFrameCallback(callback, _currentFrameTimeStamp!); //榜首处
    _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;
  }
}

榜首处并没清空 _persistentCallbacks 回调,第二处在履行完了之后,会清空 _postFrameCallbacks 回调。

所以我们经过 addPostFrameCallback 增加的回调只会履行一次。

小结

现在咱们知道了建议帧调度便是通知 Native:请调度我吧,我的回调现已预备好了!分别是 onBeginFrameonDrawFrame

总结

帧调度就说完啦,和咱们平常写的代码一样,把一个大使命分成几个阶段,每个阶段对应一个回调数组,从开端到完毕依次是:动画、布局、组成、制作、收尾

Flutter 必知必会系列 —— 从 SchedulerBinding 中看 Flutter 帧调度