SchedulerBinding 的三个Frame回调

addPersistentFrameCallback、addPostFrameCallback、addTimingsCallback 确实都是 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 ------ 一帧真上屏后报告性能。


相关推荐
FungLeo6 小时前
成为全栈·React 管理后台篇·总复盘:从“接口能调通”到“后台值得使用”
前端·react.js·前端框架·项目复盘·成为全栈
Csvn7 小时前
并发模式:让渲染学会排队、插队和让路
前端
可乐鸡翅yeah_8 小时前
新手梳理:M3U8 线上问题,哪些是前端锅,哪些是后端锅
前端·ios·音视频·实时音视频·m3u8·音视频在线播放
Flynt8 小时前
Linear 用 1000 个 PR 换掉 styled-components,我写了 200 个按钮,把这笔账复现了一遍
前端·css·preact
JavaGuide9 小时前
NVIDIA 又开源了!这次给 AI Agent 加上权限管控
前端·后端
excel9 小时前
prisma 如何处理数据库竞态
前端·数据库·后端
莪_幻尘10 小时前
Skill 体检:30 个 Skill 全凭感觉?体检器先自曝了 8 个“假 0 分
前端·人工智能·llm
风骏时光牛马10 小时前
AI模型综合能力评测:性能、指令遵循与多场景实测对比
前端
Frag0ut10 小时前
Chrome与Chromium内核浏览器在Windows 11上的新特性全景解析
前端·chrome·windows·web安全·chromium·gemini ai·playready drm
hiahiahia12311 小时前
实现完整 Tool Dispatcher
开发语言·前端