1. Choreographer 核心回调流程:doFrame 回调的触发时机、回调链组成、Vsync 信号与调度

1.1 doFrame 回调的触发时机

doFrame 是 Choreographer 帧处理流程的入口,每次系统产生垂直同步(Vsync)信号时被调用。其核心触发时机取决于 Vsync 信号到达 Choreographer 的消息队列。

  • 硬件 Vsync 信号:由显示硬件(如 GPU/Display 驱动)以固定频率(通常为 60Hz 或 90Hz)产生。
  • 软件 Vsync 转发:SurfaceFlinger 接收硬件 Vsync,通过 VsyncThread 分发到各个应用进程的 Choreographer。
  • 回调执行窗口:doFrame 在应用主线程(UI 线程)的消息队列中执行,必须在下一个 Vsync 信号到来之前完成,否则将导致丢帧。
关键概念:Vsync 信号不是直接触发 doFrame,而是通过 Choreographer 的消息入队机制,将 doFrame 包装为一个 Runnable,等待主线程 Looper 轮询处理。主线程阻塞会直接延迟 doFrame 的执行。

1.2 回调链组成:三大回调类型

Choreographer 内部维护三条回调链表,每种回调在 doFrame 的不同阶段被处理,优先级从高到低依次为:

回调类型 阶段标识 典型用途 执行顺序
INPUT CALLBACK_INPUT 处理触摸事件、按键输入 第 1 位
ANIMATION CALLBACK_ANIMATION 执行属性动画、视图动画更新 第 2 位
TRAVERSAL CALLBACK_TRAVERSAL 执行 measure、layout、draw(View 树遍历) 第 3 位(最终)

回调链表结构:每条链表以 CallbackQueue 的形式存储,每个 CallbackQueue 内部又按 action(即 Runnable)的提交顺序排队。提交回调使用 postCallback API:

// 向 TRAVERSAL 队列提交帧回调
Choreographer.getInstance().postCallback(
    Choreographer.CALLBACK_TRAVERSAL,
    new Runnable() {
        @Override
        public void run() {
            // 自定义帧回调逻辑,如 View.invalidate()
        }
    },
    null
);

1.3 Vsync 信号与调度机制

Choreographer 的调度依赖于 FrameDisplayEventReceiver,它继承自 DisplayEventReceiver,通过 JNI 接收底层 Vsync 事件。

  1. Vsync 注册:Choreographer 在首次提交回调时,通过 scheduleVsyncLocked() 向底层注册 Vsync 监听。
  2. 事件到达:底层通过 Binder 或共享内存传递 vsync timestamp 到 FrameDisplayEventReceiver.onVsync()。
  3. 消息入队:onVsync() 将 doFrame 包装为一个 Message,发送到主线程的 Handler。
  4. 回调执行:主线程 Looper 轮询到该消息,调用 doFrame(long frameTimeNanos, int frame, ...)。
调度修正(FrameSkips):
  • 如果 doFrame 执行时发现 frameTimeNanos 已经落后当前时间超过一个帧周期(16ms),则丢弃本轮帧(stale frame),并立即触发下一次 Vsync 重试。
  • 连续丢帧超过阈值(如 2 帧)会触发 JankStats 记录卡顿。

1.4 完整回调流程 SVG 框架图

下图展示了从硬件 Vsync 到 doFrame 回调链执行的全路径:

Choreographer 回调流程:Vsync → doFrame → 回调链 硬件 Vsync (60Hz) SurfaceFlinger
VsyncThread FrameDisplayEventReceiver
(onVsync)
doFrame 入队 & 执行 INPUT 回调队列 ANIMATION 回调队列 TRAVERSAL 回调队列
(Measure/Layout/Draw)
说明:蓝色→硬件层;绿色→系统服务层;紫色→接收器;红色→主线程执行

1.5 常见错误与调试

陷阱 1:主线程耗时操作导致 doFrame 延迟
如果在主线程执行 I/O、大文件读写或复杂计算,即使 Vsync 信号准时到达,doFrame 也会因 Looper 队列排队而被延时。调试时可以使用 systrace 抓取主线程 Looper 状态:
// ADB 命令抓取 5 秒 trace 包含 Choreographer 标签
adb shell atrace --app=com.your.app -b 20480 Choreographer Input View -t 5 -o /data/local/tmp/trace.atrace
陷阱 2:回调链中抛异常导致后续回调中断
doFrame 内部是按顺序执行三条回调链,若 INPUT 或 ANIMATION 回调中抛出未捕获异常,会直接跳出 doFrame,导致 TRAVERSAL 队列被跳过,造成一帧完全丢失。可通过全局异常捕获或 UncaughtExceptionHandler 监控。

1.6 性能优化基线与测量指标

指标 正常范围 告警阈值 工具
doFrame 回调耗时 < 12ms > 16ms(丢帧风险) Choreographer.FrameCallback
Vsync 到 doFrame 延迟 < 4ms > 8ms(调度延迟) systrace + FrameMetrics
TRAVERSAL 回调耗时 < 8ms > 12ms(View 过度绘制或低效布局) Profile GPU Rendering
实践建议:在 Activity 的 onCreate 中注册一个帧回调记录基准值:
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    long mLastFrameTime = 0;
    @Override
    public void doFrame(long frameTimeNanos) {
        if (mLastFrameTime != 0) {
            long delta = (frameTimeNanos - mLastFrameTime) / 1_000_000L;
            // 理想 delta = 16.7ms,超过 16ms 即可能存在卡顿
        }
        mLastFrameTime = frameTimeNanos;
        Choreographer.getInstance().postFrameCallback(this);
    }
});