Choreographer 是什么?它的作用和工作流程

先聊聊 Choreographer 这个名字。直译过来是“编舞师”,这名字起得挺形象的。你想想看,在 Android 系统里,屏幕刷新、触摸事件、动画更新……这些任务就像一群舞者,如果没人指挥,肯定乱成一团。Choreographer 就是那个指挥的人。

它的核心任务只有一个:保证每帧画面的绘制都跟上屏幕的刷新节奏。我最早接触它的时候,其实是带着问题来的——为什么我的页面会掉帧?为什么动画看着一卡一卡的?后来才发现,搞懂 Choreographer,就等于拿到了卡顿分析的“入场券”。

它到底做了什么?

说白了,Choreographer 负责调度三个关键环节:

  • 输入事件处理——处理用户的触摸、按键等操作
  • 动画计算——执行 ValueAnimator、属性动画等的每一帧回调
  • 视图绘制——触发 View 的 measure、layout、draw 流程

这三件事必须在一个垂直同步信号(VSync)周期内完成。如果做不到,系统就会跳帧。嗯,这里要注意,跳帧不是“丢帧”,而是这帧的画面根本没机会被显示到屏幕上。

核心一句话:Choreographer 通过监听 VSync 信号,统一调度输入、动画、绘制,保证它们按每 16ms(60fps)或 8.3ms(120fps)的节奏有序执行。

工作流程,我是这么看它的

我习惯把 Choreographer 的工作流程拆成三个阶段来理解。你看下面的图会更直观:

Choreographer 核心工作流程 ① VSync 信号到达 ② doFrame() 回调 ③ 输入事件处理 ④ 动画计算与更新 ⑤ View 绘制流程 ⑥ 提交帧到 GPU / 屏幕 关键指标 一帧总耗时 < 16.6ms 才不掉帧
  1. VSync 信号触发:硬件层每 16.6ms 发一个脉冲。Choreographer 内部用 FrameDisplayEventReceiver 接收这个信号。我个人习惯把这个叫“系统的心跳”。
  2. doFrame() 回调:这是入口。会检查当前帧是否已经超时——如果上一帧还没处理完,这帧直接跳过。
  3. 分派三个任务:
    • 先处理输入事件队列(Input)
    • 再执行动画回调(Animation)
    • 最后触发绘制(Traversal)——也就是我们熟悉的 measure/layout/draw
  4. 提交渲染命令:把绘制结果通过 GPU 提交给屏幕。
我的一点经验: 你在 logcat 里看到 Choreographer.doFrame 的日志时,后面跟的时间戳差值如果超过 16ms,基本就能断定这帧卡了。我曾经在一个列表滑动场景里,就是靠这个定位到了一个自定义 View 的 onDraw 里做了图片解码——巨坑。

几个你必须知道的关键点

概念 说明 个人踩坑
VSync 垂直同步脉冲,硬件信号 真机测试时有些模拟器不模拟 VSync,数据没法看
FrameCallback Choreographer 提供的帧回调 API 我经常用这个来做精确帧率统计
Skipped Frames 跳帧,doFrame 运行时发现已经晚了 曾经追过一个跳帧 bug,最后发现是主线程在做网络请求
Jank 卡顿,掉帧累积的结果 Jank 和丢帧不同,Jank 是用户能感知到的卡

怎么跟 Choreographer 打交道?

你不需要直接操作它——系统已经帮我们封装好了。但监控帧率时,可以注册自己的 FrameCallback:

Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
    override fun doFrame(frameTimeNanos: Long) {
        // frameTimeNanos: 这一帧开始的时间(纳秒)
        // 你可以在这里记录时间,计算与上一帧的间隔
        Choreographer.getInstance().postFrameCallback(this) // 继续监听下一帧
    }
})

这个回调是每帧都会触发的。我在做帧率监控工具时,就是在 doFrame 里计算两次回调的时间差,超过 16ms 就记录一次卡顿。

注意: 不要在 doFrame 里做耗时操作!它本身就在主线程执行。如果你在里面做了复杂计算,会推后绘制流程,导致连锁掉帧。我见过有人在这里做 IO 操作——嗯,那画面简直不敢看。

一个常见的误解

很多人以为 Choreographer 是 Android 4.1(Jelly Bean)才引入的。其实在更早的版本里也有类似机制,只是从 4.1 开始系统才正式使用它来统一调度 UI 绘制。我当年做 2.3 系统适配时,卡顿分析那叫一个原始——基本靠感觉。

还有一个点:Choreographer 跟 SurfaceFlinger 的关系。SurfaceFlinger 是负责合成并送显的,而 Choreographer 是应用层的调度器。两者的协作决定了最终帧率。简单来讲:Choreographer 管“生产”,SurfaceFlinger 管“消费”。