SchedulerBinding 的三个Frame回调

204 阅读3分钟

addPersistentFrameCallbackaddPostFrameCallbackaddTimingsCallback 确实都是 Flutter 框架调度中“Frame 级别”的回调,但三者触发时机完全不同, 它们分别连接着 Frame 生命周期的三个阶段。 一个详细、专业但清晰的对照和时间线讲解 👇


🧭 一、概览表

回调接口注册位置执行时机(帧流程阶段)回调线程常见用途是否每帧持续触发
addPersistentFrameCallbackSchedulerBinding在每帧开始时,绘制流程开始之前(布局、绘制前)UI Thread(Dart isolate)自定义动画、同步逻辑、监控 frame build✅ 是,每帧都会触发
addPostFrameCallbackWidgetsBinding帧绘制完成后、但在下一帧开始前(UI 树渲染结束)UI Thread(Dart isolate)在 build 完后执行一次性逻辑(如状态恢复、测量布局等)❌ 否,只执行一次
addTimingsCallbackSchedulerBinding一帧真正完成后(包括 rasterizer 渲染、GPU 提交)Engine ➜ Dart isolate性能分析、帧率统计、掉帧检测⚙️ 仅在帧真正渲染完成时触发(由 Engine 主动回调)

🧩 二、完整帧生命周期时间线图

下面是一帧从触发到结束的时间轴:

┌──────────────────────────────────────────────────────────────┐
│ 一帧的完整生命周期(Flutter Pipeline)                       │
├──────────────────────────────────────────────────────────────┤
│ vsync 信号到达 → 触发 Scheduler.handleBeginFrame             │
│      ↓                                                       │
│ ① addPersistentFrameCallback() 触发                          │ ← 每帧都会调用(UI逻辑)
│      ↓                                                       │
│ Widgets build/rebuild/layout/paint 完成                      │
│      ↓                                                       │
│ ② addPostFrameCallback() 执行                                │ ← 本帧后执行一次(UI在屏幕前执行的最后逻辑)
│      ↓                                                       │
│ UI 层命令发送给 Engine,Rasterizer 开始渲染 → GPU            │
│      ↓                                                       │
│ ③ addTimingsCallback() 触发                                 │ ← Engine 通知 Dart:此帧渲染周期完结
│      ↓                                                       │
│ 下一帧 vsync 周期开始                                         │
└──────────────────────────────────────────────────────────────┘

🧠 三、详细解释

1️⃣ addPersistentFrameCallback

  • 🔹 每当 Flutter engine 安排一次 Frame(vsync 信号触发)时调用;
  • 🔹 在 WidgetsBinding.drawFrame() 之前;
  • 🔹 多用于做动画同步,如 Ticker, AnimationController
  • 🔹 不可在里面直接调用 setState()(会导致无限循环)。
SchedulerBinding.instance.addPersistentFrameCallback((Duration timeStamp) {
  // 每帧都会执行一次
  // timeStamp:距离应用启动的时间
});

用途:开发 Frame 驱动逻辑、动画系统、心跳检测。


2️⃣ addPostFrameCallback

  • 🔹 注册一个下一帧绘制完成后执行的回调;
  • 🔹 只执行一次;
  • 🔹 常用于需要在 widget build 完之后去访问 context 或做布局调整。
WidgetsBinding.instance.addPostFrameCallback((_) {
  print("这一帧的Widget都build & render完毕之后执行");
});

用途:执行一次性的渲染后逻辑(比如弹框、滚动控制、布局测量)。


3️⃣ addTimingsCallback

  • 🔹 由 Flutter 引擎 在一帧完全渲染完成(包括 GPU 渲染)后回调;
  • 🔹 参数提供本帧的渲染性能信息:
    • build、rasterize 时间、
    • start/finish 时间戳;
  • 🔹 不一定每帧都有(只有当一帧真正提交给屏幕后才会触发)。
SchedulerBinding.instance.addTimingsCallback((List<FrameTiming> timings) {
  for (final t in timings) {
    print('build: ${t.buildDuration}, raster: ${t.rasterDuration}');
  }
});

用途:性能监控、丢帧检测、UI心跳。


🧩 四、对应的 Pipeline Phase(官方描述)

Scheduler Phase回调接口官方解释
TRANSIENT_CALLBACKSaddPersistentFrameCallback一帧准备阶段,执行持久 frame 回调
POST_FRAME_CALLBACKSaddPostFrameCallback当前帧绘制完毕后执行延迟回调
FrameTimings (engine reported)addTimingsCallback一帧渲染完成,engine 向 Dart 报告

⚙️ 五、典型使用示例汇总

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // 每帧都会触发 - 可作心跳
  SchedulerBinding.instance.addPersistentFrameCallback((timeStamp) {
    print('Persistent at ${timeStamp.inMilliseconds}');
  });

  // 当前帧绘制完后执行一次
  WidgetsBinding.instance.addPostFrameCallback((_) {
    print('Widget build done.');
  });

  // 渲染完成后(含GPU提交)
  SchedulerBinding.instance.addTimingsCallback((timings) {
    final t = timings.last;
    print('Frame done. build=${t.buildDuration}, raster=${t.rasterDuration}');
  });

  runApp(MyApp());
}

✅ 最终总结表

回调调用频率触发时机所属线程典型用途
addPersistentFrameCallback每帧每帧开始(UI build 前)UI isolate动画驱动、持续逻辑、心跳
addPostFrameCallback一次当前帧 build 后(UI 树构建完)UI isolate布局调整、逻辑收尾
addTimingsCallback条件触发一帧 GPU 渲染完成后Engine 调用 Dart性能分析、渲染监控

🔥 一句总结记忆法:

Persistent —— 每帧都来; Post —— 帧后执行一次; Timings —— 一帧真上屏后报告性能。