SchedulerBinding
从SchedulerBinding
中我们可以学习Flutter
的帧调度,在帧的不同阶段Flutter
都做了哪些工作。
帧调度分了6个阶段,idle(空闲)、transientCallbacks(瞬时状态)、midFrameMicrotasks(微任务阶段)、persistentCallbacks(持久状态)、postFrameCallbacks(帧后状态),最后再回到idle状态。
Flutter将这6种状态封装成SchedulerPhase
js
enum SchedulerPhase {
/// 没有帧在执行。
/// 此阶段 `SchedulerBinding.scheduleTask]`调度的TaskCallBack,
/// `scheduleMicrotask`调度的微任务,
/// `Timer`的回调,事件处理(例如用户的输入),
/// 还有其他的(Future/Stream等)回调可能在执行。
idle,
/// 此阶段执行由`[SchedulerBinding.scheduleFrameCallback]`调度的回调。
/// 通常这些回调将对象更新到新的动画状态。
/// 可以查看[SchedulerBinding.handleBeginFrame]
/// 此阶段执行后会将瞬时任务都清空;
transientCallbacks,
/// `transientCallbacks`阶段会产生一些微任务,就会在此阶段执行。
midFrameMicrotasks,
/// [SchedulerBinding.addPersistentFrameCallback]添加的任务会在此阶段执行,执行pipeline 的build/layout/paint。
/// 可以查看 [WidgetsBinding.drawFrame] and [SchedulerBinding.handleDrawFrame].
persistentCallbacks,
/// 帧结束阶段
/// 执行[SchedulerBinding.addPostFrameCallback]添加的回调,一般情况下会做一些帧清理工作和发起下一帧。
/// See [SchedulerBinding.handleDrawFrame].
postFrameCallbacks,
}
帧调度方法
1.scheduleWarmUpFrame
:
- 尽可能的快的调度一帧,不需要等待引擎的"Vsync"信号请求
- runApp时会直接调用此方法,以便第一帧(可能开销比较昂贵)时可以额外获得几毫秒运行时间
- 锁定帧调度,直到调度的帧完成(可查看
_warmUpFrame
和_rescheduleAfterWarmUpFrame
属性在_handleBeginFrame
、_handleDrawFrame
方法中的作用) - 如果任何调度的帧已经开始,或者已经调用了另一个
scheduleWarmUpFrame
,此调用将被忽略 - 在正常运行中,优先使用
scheduleFrame
更新显示
js
void scheduleWarmUpFrame() {
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle) {
return;
}
_warmUpFrame = true;
final bool hadScheduledFrame = _hasScheduledFrame;
PlatformDispatcher.instance.scheduleWarmUpFrame(
beginFrame: () {
handleBeginFrame(null);
},
drawFrame: () {
handleDrawFrame();
resetEpoch();
_warmUpFrame = false;
if (hadScheduledFrame) {
scheduleFrame();
}
},
);
lockEvents(() async {
await endOfFrame;
});
}
2.scheduleForcedFrame
:
- 强行调度一帧,即使在屏幕关闭的情况引擎也会调用
handleBeginFrame
- 除非必须立即调度帧,否则最好使用
scheduleFrame
,因为使用scheduleForcedFrame
会在设备应该空闲时显著增加电池消耗。 - 调度帧时忽略 [
lifecycleState
]。
js
void scheduleForcedFrame() {
if (_hasScheduledFrame) {
return;
}
...
ensureFrameCallbacksRegistered();
platformDispatcher.scheduleFrame();
_hasScheduledFrame = true;
}
3.scheduleFrame
:
- 调用此函数后,引擎最终将调用 [
handleBeginFrame
]。(此调用可能会延迟,例如,如果设备屏幕关闭,通常会延迟到屏幕开启且应用可见时)在某一帧期间调用此函数会强制调度另一帧,即使当前帧尚未完成 - 已调度的帧由操作系统提供的"Vsync"信号触发。"Vsync"信号,即垂直同步信号,与显示器刷新相关
- 注册Native侧回调到Flutter端的帧回调
js
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled) {
return;
}
ensureFrameCallbacksRegistered();
platformDispatcher.scheduleFrame();
_hasScheduledFrame = true;
}
@protected
void ensureFrameCallbacksRegistered() {
platformDispatcher.onBeginFrame ??= _handleBeginFrame;
platformDispatcher.onDrawFrame ??= _handleDrawFrame;
}
4.scheduleFrameCallback
:
- 调度给定的瞬时帧回调,将给定的回调添加到帧回调列表中,并确保帧被调度
js
int scheduleFrameCallback(
FrameCallback callback, {
bool rescheduling = false,
}) {
scheduleFrame();
_nextFrameCallbackId += 1;
_transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(
callback,
rescheduling: rescheduling,
);
return _nextFrameCallbackId;
}
总结:以上四个帧调度方法,最后都会由platformDispatcher.scheduleFrame()
调用到_handleBeginFrame
和_handleDrawFrame
Native调用帧方法(由平台调用,可查看hooks.dart
)
_handleBeginFrame
、handleBeginFrame
:
- 由引擎调用,用于准备框架生成新帧
- 调用
scheduleFrameCallback
注册的所有瞬时帧回调
js
void _handleBeginFrame(Duration rawTimeStamp) {
if (_warmUpFrame) {
// "begin frame" and "draw frame"必须严格交替,在[_handleDrawFrame]中被重置
_rescheduleAfterWarmUpFrame = true;
return;
}
handleBeginFrame(rawTimeStamp);
}
void handleBeginFrame(Duration? rawTimeStamp) {
_frameTimelineTask?.start('Frame');
_firstRawTimeStampInEpoch ??= rawTimeStamp;
_currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
if (rawTimeStamp != null) {
_lastRawTimeStamp = rawTimeStamp;
}
assert(schedulerPhase == SchedulerPhase.idle);
_hasScheduledFrame = false;
try {
// 执行瞬时态的回调任务,更新 _schedulerPhase 枚举值
_frameTimelineTask?.start('Animate');
_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;
}
}
2._handleDrawFrame
: 此方法在handleBeginFrame
之后立即调用。它会调用addPersistentFrameCallback
注册的所有回调(这些回调通常会驱动渲染管线),然后调用addPostFrameCallback
注册的回调。
js
void _handleDrawFrame() {
if (_rescheduleAfterWarmUpFrame) {
_rescheduleAfterWarmUpFrame = false;
addPostFrameCallback((Duration timeStamp) {
_hasScheduledFrame = false;
scheduleFrame();
}, debugLabel: 'SchedulerBinding.scheduleFrame');
return;
}
handleDrawFrame();
}
void handleDrawFrame() {
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in List<FrameCallback>.of(
_persistentCallbacks,
)) {
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
}
// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.of(_postFrameCallbacks);
_postFrameCallbacks.clear();
try {
for (final FrameCallback callback in localPostFrameCallbacks) {
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
}
}
} finally {
_schedulerPhase = SchedulerPhase.idle;
}
}