温故知新-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. _handleBeginFramehandleBeginFrame :
  • 由引擎调用,用于准备框架生成新帧
  • 调用 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;
    }
  }
相关推荐
0wioiw04 小时前
Apple基础(Xcode⑤-Flutter-Singbox-AI提示词)
flutter·macos·xcode
0wioiw013 小时前
Apple基础(Xcode②-Flutter结构解析)
flutter·macos·xcode
0wioiw013 小时前
Apple基础(Xcode④-Flutter-Platform Channels)
flutter·macos·xcode
bytebeats16 小时前
Flutter中的`InkWell`组件使用示例
flutter
Steven Hank17 小时前
Flutter 替换镜像源
flutter
叽哥18 小时前
dart学习第 16 节:库与包管理 —— 高效开发的关键
flutter·dart
程序员老刘18 小时前
为什么暂时不推荐Qwen3-Coder?
flutter·程序员·ai编程
叽哥1 天前
dart学习第 8 节:面向对象(下)—— 继承与多态
flutter·dart
叽哥1 天前
dart学习第 6 节:函数进阶 —— 高阶函数与闭包
flutter·dart