持续创作,加快生长!这是我参与「日新方案 6 月更文挑战」的第2天,点击查看活动详情
前面咱们介绍了 GestureBinding
,知道了 Flutter
的手势处理流程,这一篇咱们就从 SchedulerBinding
的代码中看帧调度进程。
往期精彩:
Flutter 必知必会系列 —— runApp 做了啥
Flutter 必知必会系列 —— mixin 和 BindingBase 的奇妙配合
Flutter 必知必会系列 —— 从 GestureBinding 中看 Flutter 手势处理进程
Flutter 帧的阶段
Flutter 把一帧分为了 6 个阶段,每个阶段做不同的作业,而且把帧阶段包装在枚举类 SchedulerPhase
中,作为前期预备,咱们认识一下这个类和这几个阶段是啥。
idle —— 空闲
这个阶段没有帧使命履行,在这个阶段履行的代码是:SchedulerBinding.scheduleTask
发布的 TaskCallback
、scheduleMicrotask
注册的微使命、Timer
声明的使命、用户的手势处理器、Future
和 Stream
的使命。
这里我们需求先看一下 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 建议帧调度
帧调度的办法
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;
}
就干了两件事:榜首:确保 window
的 onBeginFrame
和 onDrawFrame
回调现已设置了。
第二:调用 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';
那么 window
的 onBeginFrame
和 onDrawFrame
便是帧调度履行的内容了。
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
是一个 Map
,key
是帧的 id
,value
是这一帧在 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:请调度我吧,我的回调现已预备好了!分别是 onBeginFrame
和 onDrawFrame
。
总结
帧调度就说完啦,和咱们平常写的代码一样,把一个大使命分成几个阶段,每个阶段对应一个回调数组,从开端到完毕依次是:动画、布局、组成、制作、收尾。