Learn From The Source Code of Flutter pt.3,Scheduler

理解Scheduler,调度器

GUI系统,即Graphical User Interface(图形用户界面),从底层概念上,包含

  • 呈现(Canvas)
  • 交互(用户输入事件,Pointer)
  • 窗口管理

Flutter并不是一个完整的GUI系统,Flutter着重于Canvas, 交互和窗口管理都依赖运行平台的GUI。这些前置概念可以帮我们正确认识Flutter的定位,进而理解源码的边界和处理本源逻辑,不至于陷入其中,无法自拔。

Scheduler

binding.dart 粘合物

目的是连接Flutter层 SchedulerBindingPlatformDispatcher
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,按优先级由高到低分别是touchanimationidle. 这个场景下我们暂时不考虑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. 取任务队头,并执行任务, 我们可以看到队列的执行是通过_ensureEventLoopCallbackhandleEventLoopCallback 形成递归取任务,终止条件是_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 framepersistent 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,
}
相关推荐
EnCi Zheng2 小时前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen2 小时前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控
广州华水科技2 小时前
北斗GNSS变形监测在大坝安全监测中的应用与优势分析
前端
前端老石人2 小时前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
CAE虚拟与现实2 小时前
五一假期闲来无事,来个前段、后端的说明吧
前端·后端·vtk·three.js·前后端
Sarvartha2 小时前
三目运算符
linux·服务器·前端
晓晨的博客2 小时前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect3 小时前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding3 小时前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
GISer_Jing3 小时前
AI全栈转型_TS后端学习路线
前端·人工智能·后端·学习