Choreographer 是什么?它的作用和工作流程
先聊聊 Choreographer 这个名字。直译过来是“编舞师”,这名字起得挺形象的。你想想看,在 Android 系统里,屏幕刷新、触摸事件、动画更新……这些任务就像一群舞者,如果没人指挥,肯定乱成一团。Choreographer 就是那个指挥的人。
它的核心任务只有一个:保证每帧画面的绘制都跟上屏幕的刷新节奏。我最早接触它的时候,其实是带着问题来的——为什么我的页面会掉帧?为什么动画看着一卡一卡的?后来才发现,搞懂 Choreographer,就等于拿到了卡顿分析的“入场券”。
它到底做了什么?
说白了,Choreographer 负责调度三个关键环节:
- 输入事件处理——处理用户的触摸、按键等操作
- 动画计算——执行 ValueAnimator、属性动画等的每一帧回调
- 视图绘制——触发 View 的 measure、layout、draw 流程
这三件事必须在一个垂直同步信号(VSync)周期内完成。如果做不到,系统就会跳帧。嗯,这里要注意,跳帧不是“丢帧”,而是这帧的画面根本没机会被显示到屏幕上。
工作流程,我是这么看它的
我习惯把 Choreographer 的工作流程拆成三个阶段来理解。你看下面的图会更直观:
- VSync 信号触发:硬件层每 16.6ms 发一个脉冲。Choreographer 内部用
FrameDisplayEventReceiver接收这个信号。我个人习惯把这个叫“系统的心跳”。 - doFrame() 回调:这是入口。会检查当前帧是否已经超时——如果上一帧还没处理完,这帧直接跳过。
- 分派三个任务:
- 先处理输入事件队列(Input)
- 再执行动画回调(Animation)
- 最后触发绘制(Traversal)——也就是我们熟悉的 measure/layout/draw
- 提交渲染命令:把绘制结果通过 GPU 提交给屏幕。
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 就记录一次卡顿。
一个常见的误解
很多人以为 Choreographer 是 Android 4.1(Jelly Bean)才引入的。其实在更早的版本里也有类似机制,只是从 4.1 开始系统才正式使用它来统一调度 UI 绘制。我当年做 2.3 系统适配时,卡顿分析那叫一个原始——基本靠感觉。
还有一个点:Choreographer 跟 SurfaceFlinger 的关系。SurfaceFlinger 是负责合成并送显的,而 Choreographer 是应用层的调度器。两者的协作决定了最终帧率。简单来讲:Choreographer 管“生产”,SurfaceFlinger 管“消费”。