第4章 注册 FrameCallback:postFrameCallback、postFrameCallbackDelayed、removeCallbacks
4.1 注册与注销回调的原理
要利用 Choreographer 监控 UI 帧率,必须先注册一个 FrameCallback 实例。Choreographer 会在下一个垂直同步(VSync)信号到来时调用该回调的 doFrame 方法。框架提供了三种操作:
- postFrameCallback – 在下一个 VSync 时立即触发一次回调。
- postFrameCallbackDelayed – 延迟一段时间(毫秒)后再触发一次回调,适合错开高优先级帧任务。
- removeCallbacks – 取消尚未触发的回调,防止内存泄漏或重复执行。
关键区别: postFrameCallback 和 postFrameCallbackDelayed 都只触发一次。如果需要持续监控(如每帧采样),必须在
doFrame 末尾再次调用 postFrameCallback 重新注册。
4.2 注册与取消的完整流程
- 实现
Choreographer.FrameCallback接口的doFrame(long frameTimeNanos)方法。 - 在需要开始监控的位置(如 Activity 的
onResume)通过Choreographer.getInstance().postFrameCallback(callback)注册。 - 在回调处理完成后,如果希望继续监控,再次调用
postFrameCallback(this);否则保持不注册即可自然停止。 - 当不再需要监控(如 Activity 的
onPause或 Fragment 销毁)时,调用removeCallbacks(callback)确保回调不会在对象被回收后仍被意外触发。
4.3 API 签名与参数说明
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
postFrameCallback(FrameCallback callback) | callback:待注册的回调对象 | void | 在下一个 VSync 信号到达时触发回调 |
postFrameCallbackDelayed(FrameCallback callback, long delayMillis) | callback:回调对象;delayMillis:延迟毫秒数 | void | 延迟 delayMillis 毫秒后,在接下来的 VSync 触发回调 |
removeCallbacks(FrameCallback callback) | callback:要取消的回调对象 | void | 移除尚未执行的回调;若回调已执行或未注册,则无效果 |
4.4 典型代码示例
public class FrameMonitor implements Choreographer.FrameCallback {
private static final String TAG = "FrameMonitor";
private long mLastFrameTimeNanos = 0;
@Override
public void doFrame(long frameTimeNanos) {
if (mLastFrameTimeNanos == 0) {
mLastFrameTimeNanos = frameTimeNanos;
// 首次回调,继续注册
Choreographer.getInstance().postFrameCallback(this);
return;
}
long diffMs = (frameTimeNanos - mLastFrameTimeNanos) / 1_000_000;
Log.d(TAG, "Frame interval: " + diffMs + " ms");
mLastFrameTimeNanos = frameTimeNanos;
// 如果想持续监控,再次注册
Choreographer.getInstance().postFrameCallback(this);
}
// 在需要延迟启动的场景中使用
public void startWithDelay(long delayMs) {
Choreographer.getInstance().postFrameCallbackDelayed(this, delayMs);
}
public void stop() {
Choreographer.getInstance().removeCallbacks(this);
}
}
// 使用示例(在 Activity 中)
private FrameMonitor mMonitor = new FrameMonitor();
@Override
protected void onResume() {
super.onResume();
mMonitor.startWithDelay(100); // 延迟 100ms 后开始监控
}
@Override
protected void onPause() {
super.onPause();
mMonitor.stop(); // 及时取消注册
}
4.5 注册与回调生命周期 SVG 流程图
4.6 常见错误与避免
未及时调用 removeCallbacks – 在 Activity/Fragment 销毁前没有取消注册,导致回调持有外部引用,造成内存泄漏。
在 doFrame 中做耗时操作 – 回调运行于主线程,任何长时间任务都会阻塞 UI 刷新,应仅做轻量计算或记录。
多次重复注册 – 每次
在 doFrame 中做耗时操作 – 回调运行于主线程,任何长时间任务都会阻塞 UI 刷新,应仅做轻量计算或记录。
多次重复注册 – 每次
postFrameCallback 都会产生一个独立的回调任务,如果忘记在每次 doFrame 中重新注册,可能导致大量回调堆积。正确做法:每次只注册一次,且确保 doFrame 中按需重新注册。
4.7 性能与稳定性优化建议
- 在
onPause或onStop中调用removeCallbacks,避免后台无意义的回调。 - 监控期间避免在
doFrame中分配新对象(如 new StringBuilder),减少 GC 抖动对帧率的影响。 - 如果只需采样,可在
doFrame中通过计数控制采样频率(如每 10 帧记录一次),降低性能开销。 - 使用
postFrameCallbackDelayed延迟启动,可以避开页面初次渲染的高负载阶段,获取更平稳的初始数据。 - 调试阶段配合
android.os.Trace为回调添加标签,便于 systrace 分析帧时长。
4.8 调试技巧
// 使用 Systrace 标记回调边界
@Override
public void doFrame(long frameTimeNanos) {
Trace.beginSection("FrameMonitor.doFrame");
// ... 处理逻辑
Trace.endSection();
Choreographer.getInstance().postFrameCallback(this);
}
// 也可以通过 Log 输出线程信息确认回调所在线程
Log.d(TAG, "doFrame called on thread: " + Thread.currentThread().getName());
若日志显示回调不在主线程(大概率是错误使用线程池),需检查是否在非 UI 线程中调用了 Choreographer 注册方法。