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,
}
相关推荐
梦境之冢8 分钟前
axios 常见的content-type、responseType有哪些?
前端·javascript·http
racerun11 分钟前
vue VueResource & axios
前端·javascript·vue.js
m0_5485147728 分钟前
前端Pako.js 压缩解压库 与 Java 的 zlib 压缩与解压 的互通实现
java·前端·javascript
AndrewPerfect28 分钟前
xss csrf怎么预防?
前端·xss·csrf
Calm55031 分钟前
Vue3:uv-upload图片上传
前端·vue.js
浮游本尊36 分钟前
Nginx配置:如何在一个域名下运行两个网站
前端·javascript
m0_7482398336 分钟前
前端bug调试
前端·bug
m0_7482329239 分钟前
[项目][boost搜索引擎#4] cpp-httplib使用 log.hpp 前端 测试及总结
前端·搜索引擎
新中地GIS开发老师44 分钟前
《Vue进阶教程》(12)ref的实现详细教程
前端·javascript·vue.js·arcgis·前端框架·地理信息科学·地信
m0_748249541 小时前
前端:base64的作用
前端