温故知新-SchedulerBinding

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)

  1. _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;
    }
  }
相关推荐
2601_949575868 分钟前
JSON5 跨端三方库仓颉适配全流程
前端·javascript·flutter
法的空间18 分钟前
AI 编程时代,为什么 Dart 分析会拖慢大型项目的开发
android·flutter·ios
恋猫de小郭2 小时前
Flutter 紧急发布 3.47.8 安全升级版本,建议都升级
android·前端·flutter
好的佩奇18 小时前
Dart 之模式
android·flutter·dart
淡淡的香烟20 小时前
Flutter实现多语言切换
android·flutter·1024程序员节
事圆则缓1 天前
从零搭建一套 Flutter 架构:先跑通数据流
flutter
程序员老刘2 天前
Flutter版本选择指南:3.44稳定了11周正式转正 | 2026年10月
flutter·ai编程·客户端
chicken_h2 天前
【无标题】
flutter·打包
事圆则缓2 天前
Flutter 底层渲染引擎怎么变了:从 Skia 到 Impeller,以及迁移时的坑
flutter
码林鼠3 天前
flutter状态管理入门教程-Riverpod
开发语言·javascript·flutter