温故知新-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;
    }
  }
相关推荐
恋猫de小郭1 小时前
Android CLI 支持 AI Agent 通过 Device Streaming 调试云真机
android·前端·flutter
m0_7381858218 小时前
Flutter 鸿蒙化实战:flutter_scankit 适配 OpenHarmony,华为 ScanKit 扫码
flutter·华为·harmonyos·鸿蒙
事圆则缓1 天前
Flutter 开发鸿蒙实战:从 OpenHarmony 适配版到 HAP 构建与插件接入
flutter·华为·harmonyos
天空之城--1 天前
Android一周动态:Android 18首次官宣、Compose Material3 1.4转正(5趋势+5资讯)
android·人工智能·flutter·架构·android jetpack
恋猫de小郭1 天前
Meta 分享怎么用 AI 迁移 Compose 项目不烧心
android·前端·flutter
EatFan1 天前
Flutter 鸿蒙适配实践 2026:从环境搭建、混合渲染到 OpenTelemetry 性能基建
flutter·华为·harmonyos·openharmony·opentelemetry·鸿蒙适配·impeller
m0_738185821 天前
Flutter 鸿蒙化实战:flutter_secure_storage 适配 OpenHarmony,敏感数据安全存储
安全·flutter·华为·harmonyos·鸿蒙
传奇开心果编程2 天前
【现代声明式UI学与练】第1课 从命令式UI到声明式UI
学习·flutter·ui·swiftui·react·android jetpack
m0_738185822 天前
Flutter 鸿蒙化实战:flutter_nfc_kit 适配 OpenHarmony,NFC 读写一步到位
flutter·华为·harmonyos·鸿蒙
恋猫de小郭2 天前
KMP 又改了编译流程, Separate Compilation 禁止了 `commonMain` 的依赖穿透
android·前端·flutter