理解Scheduler,调度器
GUI系统,即Graphical User Interface(图形用户界面),从底层概念上,包含
- 呈现(Canvas)
- 交互(用户输入事件,Pointer)
- 窗口管理
Flutter并不是一个完整的GUI系统,Flutter着重于Canvas
, 交互和窗口管理都依赖运行平台的GUI。这些前置概念可以帮我们正确认识Flutter的定位,进而理解源码的边界和处理本源逻辑,不至于陷入其中,无法自拔。
Scheduler
binding.dart 粘合物
目的是连接Flutter层 SchedulerBinding
和 PlatformDispatcher
dart
/// Ensures callbacks for [PlatformDispatcher.onBeginFrame] and
/// [PlatformDispatcher.onDrawFrame] are registered.
@protected
void ensureFrameCallbacksRegistered() {
// 粘合逻辑
platformDispatcher.onBeginFrame ??= _handleBeginFrame;
platformDispatcher.onDrawFrame ??= _handleDrawFrame;
}
binding的主要目的有了,我们细化进入代码实现,看SchedulerBinding
主要提供了什么函数,提供什么样的服务。
1. 获取instance
dart
/// The current [SchedulerBinding], if one has been created.
///
/// Provides access to the features exposed by this mixin. The binding must
/// be initialized before using this getter; this is typically done by calling
/// [runApp] or [WidgetsFlutterBinding.ensureInitialized].
static SchedulerBinding get instance => BindingBase.checkInstance(_instance);
static SchedulerBinding? _instance;
2. 调度任务,
这段代码并不复杂,将需要被调度的任务放入调度队列,其中Priority
,按优先级由高到低分别是touch
、animation
、idle
. 这个场景下我们暂时不考虑Flow
的作用。
a. 创建任务对象,返回future
,加入_taskQueue
优先队列
dart
Future<T> scheduleTask<T>(
TaskCallback<T> task,
Priority priority, {
String? debugLabel,
Flow? flow,
}) {
final bool isFirstTask = _taskQueue.isEmpty;
final _TaskEntry<T> entry = _TaskEntry<T>(
task,
priority.value,
debugLabel,
flow,
);
_taskQueue.add(entry);
if (isFirstTask && !locked) {
_ensureEventLoopCallback();
}
return entry.completer.future;
}
b. 启动队列运行, 其中Timer.run()是尽可能快的执行任务。
dart
void _ensureEventLoopCallback() {
assert(!locked);
assert(_taskQueue.isNotEmpty);
if (_hasRequestedAnEventLoopCallback) {
return;
}
_hasRequestedAnEventLoopCallback = true;
Timer.run(_runTasks);
}
// Scheduled by _ensureEventLoopCallback.
void _runTasks() {
_hasRequestedAnEventLoopCallback = false;
if (handleEventLoopCallback()) {
_ensureEventLoopCallback();
} // runs next task when there's time
}
c. 取任务队头,并执行任务, 我们可以看到队列的执行是通过_ensureEventLoopCallback
和handleEventLoopCallback
形成递归
取任务,终止条件是_taskQueue.isEmpty || locked
。
dart
@visibleForTesting
@pragma('vm:notify-debugger-on-exception')
bool handleEventLoopCallback() {
if (_taskQueue.isEmpty || locked) {
return false;
}
final _TaskEntry<dynamic> entry = _taskQueue.first;
if (schedulingStrategy(priority: entry.priority, scheduler: this)) {
try {
_taskQueue.removeFirst();
entry.run();
} catch (exception, exceptionStack) {
// ignored
}
return _taskQueue.isNotEmpty;
}
return false;
}
调度frame任务
在Flutter绘制概念中,帧有两种不同的类型,分别为transient frame
和persistent frame
,即瞬态帧和持续帧。瞬态帧是在两个持久帧中间的帧,一般是动画 ,布局变化 ,用户输入响应 , 持续帧,持续帧主要是不处于变化的帧。相对的概念即瞬态帧(也叫动画帧)。当UI不需要刷新时,持续帧会响应事件处理
,布局绘制
等。
dart
// 被调用方
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled) {
return;
}
ensureFrameCallbacksRegistered();
platformDispatcher.scheduleFrame();
_hasScheduledFrame = true;
}
void main() {
// We use ViewRenderingFlutterBinding to attach the render tree to the window.
ViewRenderingFlutterBinding(
// The root of our render tree is a RenderPositionedBox, which centers its
// child both vertically and horizontally.
root: RenderPositionedBox(
child: RenderParagraph(
const TextSpan(text: 'Hello, world.'),
textDirection: TextDirection.ltr,
),
),
).scheduleFrame(); // 调用方
}
上述scheduleFrame
方法,为主动调用,和scheduleTask
对应,通过调用平台层platformDispatcher.scheduleFrame()
的方法,通知底层创建新的帧
, ensureFrameCallbacksRegistered()
保证系统帧的创建可以被回调到binding
.
- scheduleForcedFrame: 忽略
lifecycleState
即不关心framesEnabled
- scheduleWarmUpFrame: 不等待系统Vsync,尽快调用。下方是一个实例。
dart
void runApp(Widget app) {
final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
assert(binding.debugCheckZone('runApp'));
binding
..scheduleAttachRootWidget(binding.wrapWithDefaultView(app))
..scheduleWarmUpFrame();
}
上述主要提供给Flutter调用方,供应用使用来调度任务和帧
渲染(rendering pipeline)回调
dart
void addPostFrameCallback(FrameCallback callback, {String debugLabel = 'callback'}) {
assert(() {
if (debugTracePostFrameCallbacks) {
final FrameCallback originalCallback = callback;
callback = (Duration timeStamp) {
Timeline.startSync(debugLabel);
try {
originalCallback(timeStamp);
} finally {
Timeline.finishSync();
}
};
}
return true;
}());
_postFrameCallbacks.add(callback);
}
tip: 这里有两个有用的技巧,保存
callback
的引用,将callback
指向新的对象,并放到_postFrameCallbacks
, 这样可以避免UI界面被回收引发悬停指针
。 第二个技巧是,可以通过asset的不同返回值,中断代码的执行。
上述代码中,Timeline.startSync()
是开启同步,涉及sky_engine
, 我们暂时不做深究, 只需要明确这里的调用,具体的说addPostFrameCallback callback
的调用是同步的。方法声明上解释,只会在rendering pipeline
为空时,也就是最后一个持续帧调用完成后,才会回调注册的callback
。我们可以验证一下。
dart
void handleDrawFrame() {
assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
_frameTimelineTask?.finish(); // end the "Animate" phase
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();
Timeline.startSync('POST_FRAME');
try {
for (final FrameCallback callback in localPostFrameCallbacks) {
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
}
} finally {
Timeline.finishSync();
}
} finally {
_schedulerPhase = SchedulerPhase.idle;
_frameTimelineTask?.finish(); // end the Frame
assert(() {
if (debugPrintEndFrameBanner) {
debugPrint('▀' * _debugBanner!.length);
}
_debugBanner = null;
return true;
}());
_currentFrameTimeStamp = null;
}
}
上述代码,清晰展示了handleDrawFrame
的调用逻辑,在持续帧完成,并通知之后,才开始通知_postFrameCallbacks
, 我们可以明确,动画的,瞬态帧并不会触发我们addPostFrameCallback
添加的callback
, 这也是我们可以在界面中使用他来监听界面已经渲染完成
, 而不必使用延迟等不可靠的操作。
Tip: localPostFrameCallbacks的中间copy,可以保证不阻塞新事件的加入
补充:
- 保证update是有意义的,也就是在空闲态再进行更新
dart
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
- 四种调度的状态
dart
enum SchedulerPhase {
idle,
transientCallbacks,
midFrameMicrotasks,
postFrameCallbacks,
}