SchedulerBinding 的三个Frame回调

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


🧭 一、概览表

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

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

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

scss 复制代码
┌──────────────────────────────────────────────────────────────┐
│ 一帧的完整生命周期(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()(会导致无限循环)。
dart 复制代码
SchedulerBinding.instance.addPersistentFrameCallback((Duration timeStamp) {
  // 每帧都会执行一次
  // timeStamp:距离应用启动的时间
});

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


2️⃣ addPostFrameCallback

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

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


3️⃣ addTimingsCallback

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

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


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

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

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

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 ------ 一帧真上屏后报告性能。


相关推荐
作业逆流成河5 分钟前
别再一次性重构枚举了:如何把一个真实后台项目的状态字典,渐进式迁移到enum-plus?
前端·javascript·开源
暗不需求5 分钟前
React 性能优化秘籍:深入理解 `useMemo` 与 `useCallback`
前端·react.js·面试
stringwu5 分钟前
Flutter 复杂拖拽排序实战:同源排序 + 跨容器拖拽完整落地
flutter
专注VB编程开发20年11 分钟前
我制作excel工作簿的选项卡,发给deep seek, 昨天修改了一天
前端·vue.js·excel
light blue bird17 分钟前
工序路径主子表单工序组装图表组件
前端·数据库·信息可视化·.net·web端·razor page
linlinlove229 分钟前
前端uniapp、后端thinkphp股票系统开发功能展示、代码披露、HQChart
前端·uni-app·echarts·thinkphp·hqchart·配资·deepseek选股票
万少32 分钟前
Claude Code 任务结束会自己喊你:一个 Stop Hook 搞定提示音
前端·后端·代码规范
ZC跨境爬虫40 分钟前
跟着 MDN 学CSS day_30:(玩转列表样式,从基础到进阶)
前端·css·html·tensorflow·媒体
ct9781 小时前
TypeScript 中的泛型
前端·javascript·typescript
IT_陈寒1 小时前
React hooks闭包陷阱把我坑惨了,原来这才是正确用法
前端·人工智能·后端