前言
一切皆Widget,良好的底层设计都会屏蔽底层的逻辑,Java如此,Flutter亦是如此,甚至还有开发者面向Getx编程,那么我们可以做如是类比,Flutter是J2EE, Getx是Spring套件,作为Java后台开发,面向Spring开发是不够的,正如,跨平台Flutter 不了解底层机制
, 也无法分析和解决非逻辑问题
。所以请花三分钟阅读本文,了解基础原理就好。其他相关的文章在掘金上,我就不复制了,请移步武当山王也
理解Scheduler,调度器
GUI系统,即Graphical User Interface(图形用户界面),从底层概念上,包含
- 呈现(Canvas)
- 交互(用户输入事件,Pointer)
- 窗口管理
Flutter并不是一个完整的GUI系统,Flutter着重于Canvas
, 交互和窗口管理都依赖运行平台的GUI。这些前置概念可以帮我们正确认识Flutter的定位,进而理解源码的边界和处理本源逻辑,不至于陷入其中,无法自拔。
Scheduler
binding.dart 粘合物
目的是连接Flutter层 SchedulerBinding
和 PlatformDispatcher
/// Ensures callbacks for [PlatformDispatcher.onBeginFrame] and/// [PlatformDispatcher.onDrawFrame] are registered.void ensureFrameCallbacksRegistered() {// 粘合逻辑platformDispatcher.onBeginFrame ??= _handleBeginFrame;platformDispatcher.onDrawFrame ??= _handleDrawFrame;}
binding的主要目的有了,我们细化进入代码实现,看SchedulerBinding
主要提供了什么函数,提供什么样的服务。
1. 获取instance
/// 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
优先队列
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()是尽可能快的执行任务。
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
。
('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不需要刷新时,持续帧会响应事件处理
,布局绘制
等。
// 被调用方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,尽快调用。下方是一个实例。
void runApp(Widget app) {final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();assert(binding.debugCheckZone('runApp'));binding..scheduleAttachRootWidget(binding.wrapWithDefaultView(app))..scheduleWarmUpFrame();
}
上述主要提供给Flutter调用方,供应用使用来调度任务和帧
渲染(rendering pipeline)回调
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
。我们可以验证一下。
void handleDrawFrame() {assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);_frameTimelineTask?.finish(); // end the "Animate" phasetry {// 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 Frameassert(() {if (debugPrintEndFrameBanner) {debugPrint('▀' * _debugBanner!.length);}_debugBanner = null;return true;}());_currentFrameTimeStamp = null;}}
上述代码,清晰展示了handleDrawFrame
的调用逻辑,在持续帧完成,并通知之后,才开始通知_postFrameCallbacks
, 我们可以明确,动画的,瞬态帧并不会触发我们addPostFrameCallback
添加的callback
, 这也是我们可以在界面中使用他来监听界面已经渲染完成
, 而不必使用延迟等不可靠的操作。
Tip: localPostFrameCallbacks的中间copy,可以保证不阻塞新事件的加入
补充:
- 保证update是有意义的,也就是在空闲态再进行更新
void ensureVisualUpdate() {switch (schedulerPhase) {case SchedulerPhase.idle:case SchedulerPhase.postFrameCallbacks:scheduleFrame();return;case SchedulerPhase.transientCallbacks:case SchedulerPhase.midFrameMicrotasks:case SchedulerPhase.persistentCallbacks:return;}}
- 四种调度的状态
enum SchedulerPhase {idle,transientCallbacks,midFrameMicrotasks,postFrameCallbacks,
}