第4章 注册 FrameCallback:postFrameCallback、postFrameCallbackDelayed、removeCallbacks

4.1 注册与注销回调的原理

要利用 Choreographer 监控 UI 帧率,必须先注册一个 FrameCallback 实例。Choreographer 会在下一个垂直同步(VSync)信号到来时调用该回调的 doFrame 方法。框架提供了三种操作:

  • postFrameCallback – 在下一个 VSync 时立即触发一次回调。
  • postFrameCallbackDelayed – 延迟一段时间(毫秒)后再触发一次回调,适合错开高优先级帧任务。
  • removeCallbacks – 取消尚未触发的回调,防止内存泄漏或重复执行。
关键区别: postFrameCallback 和 postFrameCallbackDelayed 都只触发一次。如果需要持续监控(如每帧采样),必须在 doFrame 末尾再次调用 postFrameCallback 重新注册。

4.2 注册与取消的完整流程

  1. 实现 Choreographer.FrameCallback 接口的 doFrame(long frameTimeNanos) 方法。
  2. 在需要开始监控的位置(如 Activity 的 onResume)通过 Choreographer.getInstance().postFrameCallback(callback) 注册。
  3. 在回调处理完成后,如果希望继续监控,再次调用 postFrameCallback(this);否则保持不注册即可自然停止。
  4. 当不再需要监控(如 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 流程图

FrameCallback 注册与取消流程 创建FrameCallback实例 postFrameCallback postFrameCallbackDelayed Choreographer调度 doFrame执行 removeCallbacks 再次postFrameCallback

4.6 常见错误与避免

未及时调用 removeCallbacks – 在 Activity/Fragment 销毁前没有取消注册,导致回调持有外部引用,造成内存泄漏。
在 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 注册方法。