本文介绍了 App 开发者不经常接触到但是在 Android Framework 渲染链路中非常重要的一个类 Choreographer。包括 Choreographer 的引入背景、Choreographer 的简介、部分源码解析、Choreographer 与 MessageQueue、Choreographer 和 APM,以及手机厂商基于 Choreographer 的一些优化思路
Choreographer 让动画、部分输入处理和 View 更新围绕显示时序协调执行。它在有待处理工作时请求帧回调,而不是每个物理刷新周期都无条件唤醒 App。2019 年常见的 60Hz 屏幕刷新间隔约为 16.67ms;90Hz、120Hz 分别约为 11.11ms、8.33ms,现代设备还可能动态切换刷新率。回调及时不等于画面已经呈现,后续还涉及 RenderThread、GPU 和合成显示链路。了解这套流程,也有助于理解 Message、Handler、Looper、MessageQueue、Measure、Layout 和 Draw。
版本说明(2026-10-13 技术复核): 旧文源码摘录以 AOSP
android-10.0.0_r1的 Choreographer 为参照,保留 2019 年截图;补充 Android 16 实现和 API 24/31/33 的观测入口。源码节选不是独立可编译应用,厂商实现和截图配置未完整提供;旧私有接口已在相应章节标注。
本文是 Systrace 系列文章的第八篇,主要是对 Systrace 中的 Choreographer 进行简单介绍
本系列的目的是通过 Systrace 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章,但是总是记不住代码,或者不清楚其运行的流程,也许从 Systrace 这个图形化的角度,你可以理解的更深入一些。
系列文章目录
- Systrace 简介
- Systrace 基础知识 - Systrace 预备知识
- Systrace 基础知识 - Why 60 fps ?
- Systrace 基础知识 - SystemServer 解读
- Systrace 基础知识 - SurfaceFlinger 解读
- Systrace 基础知识 - Input 解读
- Systrace 基础知识 - Vsync 解读
- Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解
- Systrace 基础知识 - MainThread 和 RenderThread 解读
- Systrace 基础知识 - Triple Buffer 解读
- Systrace 基础知识 - CPU Info 解读
- Systrace 流畅性实战 1 :了解卡顿原理
- Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析
- Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问
- Systrace 响应速度实战 1 :了解响应速度原理
- Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例
- Systrace 响应速度实战 3 :响应速度延伸知识
- Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇
- Systrace 线程 CPU 运行状态分析技巧 - Running 篇
- Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇
主线程运行机制的本质
这篇文章对应后续的 Perfetto 5:Android App 基于 Choreographer 的渲染流程。Android 主线程通过 Looper 分发 Message;不是 MessageQueue 自己执行应用代码。等待下一条消息时,native poll 也可能处理文件描述符事件,VSync 接收正与此有关,所以“主线程所有操作都是 Message”只是过度简化。
在 Perfetto 中,把问题帧与主线程、RenderThread、GPU 和 SurfaceFlinger 对齐。Android 12 / API 31 及以上,且采集包含对应数据时,可利用 FrameTimeline 的 expected/actual 轨道定位超期;缺轨道不等于零掉帧。线程 Sleep、Runnable 和 Running 要分别调查,不能把 slice 墙钟时长直接当 CPU 时间。下图展示的是一个有间隔的历史分发过程,MethodTrace 插桩也会影响时间。
MethodTrace 图示

Systrace 图示

演进
Choreographer 在 Android 4.1 / API 16 引入,用来协调显示时序上的工作。下面两张原图中已经有 Choreographer.doFrame 和 RenderThread,因此不能把它们当成“引入 Choreographer 之前的 Android”证据。它们展示了另一组较密集的渲染和等待;具体系统配置没有随图提供。
MethodTrace 图示


图中能看到 RenderThread 的 dequeueBuffer 区间,但仅凭这张图不能证明唯一瓶颈或固定消费速率。可用 Buffer、消费者进度和 fence 等待都需要进一步核对;不是 SurfaceFlinger 以绝对固定速度直接消费一个“Front Buffer”。
稳定的呈现节奏很重要,但“稳定 50fps 一定比 40–60fps 波动更流畅”不能一概而论。在固定 60Hz 显示上,50fps 本身也无法逐帧按整数刷新间隔均匀呈现。VSync 协调与缓冲机制能改善 pacing,并不保证稳定帧率或消除工作量。
引入 Choreographer
Choreographer 请求下一次适用的 VSync 事件,由绑定 Looper 的线程调度回调。它不负责自行改变屏幕刷新率,App 的 VSync 回调也不是直接运行在硬件中断上下文。没有待处理工作时,普通 View 窗口不需要持续产生新帧。

这张原示意图需要按简化图阅读:RenderThread 是 CPU 线程,图中把它的工作框整体写成“GPU”不准确;获取 Buffer、提交 GPU 命令、入队和合成并非图上每格都严格串行。16.6ms 只是当时 60Hz 的近似刷新间隔,不代表任意一帧的所有阶段都在这一段内完成,也不是端到端输入延迟。高刷背景可参考90Hz 漫谈。
Choreographer 简介
Choreographer 接收有时限的帧回调,按阶段执行到期工作,并请求下一次需要的显示事件。Android 10 的顺序是 INPUT → ANIMATION → INSETS_ANIMATION → TRAVERSAL → COMMIT;这不是把 App 的所有 Message 都收进来,也不意味着每帧必做完整 measure/layout/draw。
Choreographer、SurfaceFlinger 和缓冲机制共同参与帧调度,但不能保证 App 固定达到 20、60 或 90fps。监控中还要区分公开 FrameCallback、隐藏的 FrameInfo、Looper 消息计时与最终呈现数据。IdleHandler 在消息队列满足空闲条件时运行,不是“每帧结束”的固定通知。
另外虽然画图是一个比较好的解释流程的好路子,但是我个人不是很喜欢画图,因为平时 Systrace 和 MethodTrace 用的比较多,Systrace 是按从左到右展示整个系统的运行情况的一个工具(包括 cpu、SurfaceFlinger、SystemServer、App 等关键进程),使用 Systrace 和 MethodTrace 也可以很方便地展示关键流程。当你对系统代码比较熟悉的时候,看 Systrace 就可以和手机运行的实际情况对应起来。所以下面的文章除了一些网图之外,其他的我会多以 Systrace 来展示。
从 Systrace 的角度来看 Choreogrepher 的工作流程
下图以桌面滑动为例。绿色 F 是旧版 Systrace 的帧分析标记,不能独自证明这一帧最终显示成功。灰白背景代表该 Trace 的 VSync 参考间隔,具体数值要看时间轴和采集配置,不能从颜色直接认定 16.6ms。
典型硬件加速 View 路径中,主线程更新绘制状态,RenderThread 准备并提交渲染工作;Buffer 入队后还要与 GPU 完成、SurfaceFlinger 合成和实际呈现关联。UI/RT 可以重叠,不能简单将两段墙钟时长相加作为显示耗时。

有了上面这个整体的概念,我们将 UI Thread 的每一帧放大来看,看看 Choreogrepher 的位置以及 Choreogrepher 是怎么组织每一帧的

Choreographer 的工作流程
- 初始化线程内 Choreographer,绑定 Looper/FrameHandler,创建显示事件接收器与回调队列。
- 有到期工作且尚未安排帧时,请求 VSync;事件到达后,
onVsync将一个异步 Runnable 放入同一 Looper 的队列,之后run()执行doFrame()。 doFrame核对帧是否仍需要执行,计算开始处理相对帧时间的延迟,必要时校正动画帧时间。- 依次执行到期的 INPUT、ANIMATION、INSETS_ANIMATION、TRAVERSAL、COMMIT 回调。Traversal 中可能把绘制状态同步给 RenderThread。
- RenderThread 提交 GPU 工作与 Buffer;提交返回不表示 GPU 已完成。相关 fence 描述工作完成/使用约束,SurfaceFlinger/HWC 后续才能按依赖安排合成和显示。
- 动画等仍有后续工作时再次安排帧;并非无条件永久循环。
上图的旧标注把 Traversal 写作第三项,省略了 Insets Animation;Android 10 源码中它是第四类。图中“渲染完成抛 Buffer”也应按上述异步 GPU/fence 边界理解。
同时也附上这一帧所对应的 MethodTrace(这里预览一下即可,下面会有详细的大图)

下面我们就从源码的角度,来看一下具体的实现
源码解析
以下为 Android 10 的源码节选,注释中的省略部分包含其他检查和状态维护,不可把多个片段拼成完整类。Android 16 的 doFrame 已接收 VsyncEventData,使用事件携带的 frame interval、vsync id 和 deadline 等信息;签名与旧片段不同,见版本化源码。
Choreographer 的初始化
Choreographer 的单例初始化
1 | // Thread local storage for the choreographer. |
Choreographer 的构造函数
1 | private Choreographer(Looper looper, int vsyncSource) { |
FrameHandler
1 | private final class FrameHandler extends Handler { |
Choreographer 初始化链
新窗口的 Activity 恢复显示过程中,窗口添加路径可以创建 ViewRootImpl,并取得当前线程的 Choreographer。它也可能被动画等更早初始化,不能把这条链当成唯一创建时机;已有 ViewRootImpl 的恢复也不必重建。下面是调用关系示意:
1 | ActivityThread.handleResumeActivity(IBinder, boolean, boolean, String) (android.app) |
FrameDisplayEventReceiver 简介
Vsync 的注册、申请、接收都是通过 FrameDisplayEventReceiver 这个类,所以可以先简单介绍一下。 FrameDisplayEventReceiver 继承 DisplayEventReceiver , 有三个比较重要的方法
- onVsync – Vsync 信号回调
- run – 执行 doFrame
- scheduleVsync – 请求 Vsync 信号
1 | private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { |
scheduleVsync() 的实现位于父类 DisplayEventReceiver,不应把访问其私有 mReceiverPtr 的代码直接搬进子类。FrameHandler 中的 MSG_DO_FRAME 分支主要是禁用 VSync 时的定时回退;正常 VSync 路径使用上面这个 Runnable。
Choreographer 中 Vsync 的注册
从下面的函数调用栈可以看到,Choreographer 的内部类 FrameDisplayEventReceiver.onVsync 负责接收 Vsync 回调,通知 UIThread 进行数据处理。
那么 FrameDisplayEventReceiver 是通过什么方式在 Vsync 信号到来的时候回调 onVsync 呢?答案是 FrameDisplayEventReceiver 的初始化的时候,最终通过监听文件句柄的形式,其对应的初始化流程如下
android/view/Choreographer.java
1 | private Choreographer(Looper looper, int vsyncSource) { |
android/view/Choreographer.java
1 | public FrameDisplayEventReceiver(Looper looper, int vsyncSource) { |
android/view/DisplayEventReceiver.java
1 | public DisplayEventReceiver(Looper looper, int vsyncSource) { |
这里要区分控制请求和事件传送。DisplayEventReceiver::requestNextVsync()通过 IDisplayEventConnection 的 Binder 方法请求下一次 VSync;事件则从 BitTube 通道读回。不是“请求和回调都通过同一个 socket 发消息”。
DisplayEventDispatcher::initialize()把接收端 FD 加入 native Looper。FD 可读时分发事件到 Java onVsync(),后者再投递异步 Runnable,因此 onVsync 与 doFrame 是两步,不是 App 执行硬件中断。原图展示了这两个阶段:

Choreographer 处理一帧的逻辑
Choreographer 处理绘制的逻辑核心在 Choreographer.doFrame 函数中,从下图可以看到,FrameDisplayEventReceiver.onVsync post 了自己,其 run 方法直接调用了 doFrame 开始一帧的逻辑处理
android/view/Choreographer.java
1 | public void onVsync(long timestampNanos, long physicalDisplayId, int frame) { |
doFrame 函数主要做下面几件事
- 估计开始处理相对帧时间的延迟
- 记录帧绘制信息
- 执行 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_INSETS_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT
计算掉帧逻辑
1 | void doFrame(long frameTimeNanos, int frame) { |
这里计算的是 startNanos - frameTimeNanos:当前 doFrame 开始处理时,距离所携带的帧时间过去了多久。整数除以该版本的帧间隔,得到错过的调度间隔数;它不是这帧绘制耗时,也不是完整的实际呈现掉帧数。源码还会调整用于动画的 frameTimeNanos,并处理时间倒退等情况。

图中 start_time 应理解为事件携带的帧时间戳,不是 Java 收到回调那一刻的系统时间;分母也不能固定为 16ms。

前一段长工作可能拖延下一次回调,但“这只是在算前一帧掉帧”并不准确。GPU、合成或呈现超期可能没有对应的这条警告,主线程处理延迟也可能来自 Runnable 调度等待,而不全是业务代码在 CPU 上忙。图中最后一处 start_time - end_time 还写反了,源码计算方向为开始处理时间减帧时间戳。
记录帧绘制信息
Choreographer 使用内部 android.graphics.FrameInfo记录部分阶段时间,HWUI 再补充后续字段。它带 @hide,不是公开给普通 App 的稳定 SDK 类。阶段开始时间也不是每个业务回调的独立耗时;下面是 Android 10 的数组索引,不能用固定下标解析所有新版输出。
从 FrameInfo 这些标志就可以看出记录的内容,后面我们看 dumpsys gfxinfo 的时候数据就是按照这个来排列的
1 | // Various flags set to provide extra metadata about the current frame |
doFrame 函数记录从 Vsync time 到 markPerformTraversalsStart 的时间
1 | void doFrame(long frameTimeNanos, int frame) { |
执行 Callbacks
1 | void doFrame(long frameTimeNanos, int frame) { |
Input 回调调用栈
input callback 一般是执行 ViewRootImpl.ConsumeBatchedInputRunnable
android/view/ViewRootImpl.java
1 | final class ConsumeBatchedInputRunnable implements Runnable { |
这里展示的是批量输入消费后进入 DecorView/Activity/View 事件分发的一个路径。并非所有输入都要等待 INPUT 回调,按键、非批量事件及其他分发路径要分别分析。

Animation 回调调用栈
一般我们接触的多的是调用 View.postOnAnimation 的时候,会使用到 CALLBACK_ANIMATION
1 | public void postOnAnimation(Runnable action) { |
那么一般是什么时候回调用到 View.postOnAnimation 呢,我截取了一张图,大家可以自己去看一下,接触最多的应该是 startScroll,Fling 这种操作

具体调用栈取决于所提交的回调。下面图中能看到 AnimationHandler、ValueAnimator/AnimatorSet 等动画计算;仅凭这张图不能把它确定成 AbsListView 的 FlingRunnable。

另外我们的 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION
1 | public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { |
Traversal 调用栈
1 | void scheduleTraversals() { |
以上 ViewRootImpl 片段只保留条件分支骨架,局部变量来自省略的外围逻辑。不是每帧都执行 measure/layout/draw;屏障也不提升 CPU 调度优先级,其作用见后面的 MessageQueue 章节。
doTraversal 的 TraceView 示例

下一帧的 Vsync 请求
由于动画、滑动、Fling 这些操作的存在,我们需要一个连续的、稳定的帧率输出机制。这就涉及到了 Vsync 的请求逻辑,在连续的操作,比如动画、滑动、Fling 这些情况下,每一帧的 doFrame 的时候,都会根据情况触发下一个 Vsync 的申请,这样我们就可以获得连续的 Vsync 信号。
看下面的 scheduleTraversals 调用栈(scheduleTraversals 中会触发 Vsync 请求)
invalidate/requestLayout 可以促成 traversal 调度,但请求会合并,且取决于 View 状态等条件,不是每调用一次就各产生一个 VSync。
我们下面以 Animation 为例,看看 Animation 是如何驱动下一个 Vsync ,来持续更新画面的
ObjectAnimator 动画驱动逻辑
android/animation/ObjectAnimator.java
1 | public void start() { |
android/animation/ValueAnimator.java
1 | private void start(boolean playBackwards) { |
android/animation/AnimationHandler.java
1 | public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) { |
调用 postFrameCallback 会走到 mChoreographer.postFrameCallback ,这里就会触发 Choreographer 的 Vsync 请求逻辑
android/animation/AnimationHandler.java
1 | public void postFrameCallback(Choreographer.FrameCallback callback) { |
android/view/Choreographer.java
1 | private void postCallbackDelayedInternal(int callbackType, |
上述动画循环在回调列表仍有待执行工作时重新 post。公开的 FrameCallback 单次执行后会移除,连续动画由 AnimationHandler 主动续订;普通 App 自己写循环也要在停止/不可见时取消。
动画过程中一帧的 TraceView 示例

源码小结
getInstance()取得当前 Looper 线程的 Choreographer,不是整个进程唯一一个;无 Looper 时会抛异常。显示事件和 doFrame 都在绑定线程上处理。- VSync 请求走显示连接,事件经 FD/Looper 分发,再以异步 Message 执行 doFrame;不是在 App 中处理显示硬件中断。
FrameCallback单次有效,不能当成自动常驻订阅。FrameCallback 位于 ANIMATION 阶段,执行一次不保证生成新 Buffer。- Android 10/16 都按 INPUT、ANIMATION、INSETS_ANIMATION、TRAVERSAL、COMMIT 顺序处理到期队列;前面阶段的状态变化可以影响后面的 traversal。
- COMMIT 在 traversal 后执行,不是 GPU/SurfaceFlinger 完成或实际呈现通知。ActivityThread.scheduleTrimMemory()会在有主线程 Choreographer 时把内存整理安排到这一阶段,但这只是用途之一,也不是每帧都会调用 onTrimMemory。
- ListView 的 obtain/setup 可能由输入滚动或动画继续滚动触发,也可能出现在布局路径。应根据实际栈区分,不能只按阶段名猜调用原因。
APM 与 Choreographer
不同入口观测的是不同阶段,不能把所有数字都叫“FPS”或“掉帧数”:
FrameCallback:观察所在 Looper 的帧回调节奏,不是 Buffer/呈现计数。dumpsys gfxinfo ... framestats:查看对应窗口的 HWUI 帧统计;按该设备输出的字段解释。SurfaceFlinger --latency:旧版的帧时间统计,需匹配真实 layer 和系统实现。- PageFlip 私有事务:仅保留为历史说明,Android 16 的旧查询已不可用。
- Choreographer skip warning:开始处理的延迟提示,非完整呈现判据。
- Looper/BlockCanary:消息分发耗时,非帧显示耗时。
现代入口中,Android 7 / API 24 提供 Window.OnFrameMetricsAvailableListener;回调通知也可能丢失,dropCountSinceLastInvocation 不是画面掉帧数。Android 12 / API 31 起,在有数据的设备/采集中可用 Perfetto FrameTimeline关联 expected/actual 和应用/SF 帧,继续定位 CPU、GPU 或合成延迟。
利用 FrameCallback 的 doFrame 回调
FrameCallback 接口
1 | public interface FrameCallback { |
接口使用
以下代码应在已有 Looper 的线程上使用;普通 View 应用通常是主线程:
1 | Choreographer choreographer = Choreographer.getInstance(); |
接口处理
1 | // Android 10: postFrameCallbackDelayed() delegates to this internal queue. |
postFrameCallback() 从 API 16 起公开,每次只调用一次。持续观察需要再次 post 并管理停止条件;这种探针本身也会继续请求帧,不能把由它驱动的回调次数等同于 App 实际绘制次数。frameTimeNanos 是框架给出的动画帧时间,可能经过延迟校正,不等同于回调实际开始时间。
TinyDancer是原文参考的历史项目,使用 Choreographer 回调估计节奏;它展示的数字不能直接代表合成后屏幕 FPS。API 33 还增加 postVsyncCallback(),可在回调内通过 FrameData/FrameTimeline 读取候选 deadline 和 expected presentation time;这些是调度预测,不是实际呈现测量,数据也不应脱离回调生命周期持有使用。
利用 FrameInfo 进行监控
1 | adb shell dumpsys gfxinfo com.meizu.flyme.launcher framestats |
包名应替换为现场目标。下面保留原始样本;其窗口标为 StatusBar,不能声称它就是上面 Launcher 命令在同次复现中的输出。
1 | Window: StatusBar |
不要省略 CSV 表头:不同 Android/HWUI 版本可能增加或调整字段。时间戳以对应实现的单调时钟为基准,单位纳秒;Duration 字段是时长,不能把整行每列都当时间点。旧 OldestInputEvent=9223372036854775807 与 NewestInputEvent=0 是无有效输入时间的哨兵组合,不是巨大的输入延迟。FrameCompleted 不等于屏幕实际呈现时刻;各类慢帧计数可能重叠,也不应相加作为不同坏帧的总数。
利用 SurfaceFlinger 进行监控
下面是 Android 10 时代的诊断入口示例,先列出实际 layer,再查询目标;名称会随窗口、重建及版本变化:
1 | adb shell dumpsys SurfaceFlinger --list |
按 Android 10 SurfaceFlinger::dumpStatsLocked()和 FrameTracker::dumpStats():第一行为刷新间隔,后续三列按顺序为:
- A:
desiredPresentTime,期望呈现时间。 - B:
actualPresentTime,记录的实际呈现时间。 - C:
frameReadyTime,帧内容可用于呈现的时间,可来自 ready fence。
它们不是“App 开始画”“提交硬件前 VSync”“硬件接受完”的三个时刻。该版 ring buffer 大小为 128,而每个 tracker 的 dump 循环输出 127 个槽位;零值、未就绪的 INT64_MAX 等不是有效帧。只统计目标、时间范围和有效记录,先验证单调性、缺失和刷新率变化,不能假设每次命令得到 128 个新帧。
原文的 ceil((C - A) / refresh_period) 及“相邻值变化就是掉帧”不构成通用判据:期望、就绪和实际呈现是不同阶段,稳定的长延迟甚至可能让该值保持不变。应根据该版本的数据定义分析实际呈现间隔/预期 deadline;支持时优先关联 FrameTimeline,不能沿用公式自动认定 jank。
利用 SurfaceFlinger PageFlip 机制进行监控
历史接口,不能照搬为当前 App 监控方案。 Android 10 私有调试事务 1013 返回默认显示的 PageFlip 计数;即使在允许访问的系统上,它也是显示级计数,不是某个 App 的帧数。原文命令还少了 shell 层;补上 adb shell service call SurfaceFlinger 1013 也不改变其私有、受权限控制的性质。
Android 16 源码已明确:
1 | case 1013: // Unused. |
因此移除把 transact(1013) 的返回值直接读成计数并计算 App FPS 的旧示例,改用上面的公开窗口指标或有版本依据的 Trace 数据。不能忽略 transact/返回状态,也不能把不支持返回当作零帧。
Choreographer 自身的掉帧计算逻辑
Android 10 的 SKIPPED_FRAME_WARNING_LIMIT 默认是 30,由启动时读取的 debug.choreographer.skipwarning 属性配置。它控制日志阈值,并不意味着少于 30 个错过间隔就不卡,也不是每帧完整测量。
1 | if (jitterNanos >= mFrameIntervalNanos) { |
要区分帧时间、doFrame 实际开始、工作完成与最终呈现;改变告警阈值不会修复这些阶段的延迟。
BlockCanary
BlockCanary 一类工具利用 Looper 的消息分发前后日志做计时。下面摘录的是旧版分发边界:
1 | // Excerpt inside Looper.loop(); obtaining the next message is omitted. |
测得的是一次 dispatch 的墙钟时长,包括其中的等待;它不是纯 CPU 时间,不涵盖 RenderThread/GPU/SF 的完整链路,也不覆盖 dispatch 之外所有 native poll 回调。短消息连续排队造成的延迟也可能没有单条“长消息”。监控本身的字符串处理、日志和采样有开销,setMessageLogging 的使用还需与其他观察者协调。不要把日志监听等同于零开销、全覆盖的掉帧检测。
MessageQueue 与 Choreographer
在 Android 10 的 MessageQueue.next()里,当队首是 target == null 的同步屏障时,会向后查找异步消息,并仍检查它是否到达 when。屏障后的同步消息需等屏障移除;这不是抢占正在执行的消息,也不会提高线程的 CPU 调度优先级。
Message.setAsynchronous()从 API 22 起公开,原文“隐藏方法”的说法已过期;Handler.createAsync()从 API 28 起公开。异步表示可以越过同步屏障,不表示自动放到队首、另开线程或必定立即执行。没有屏障时,它仍受队列时间和顺序约束;框架的 post/removeSyncBarrier 本身不是普通 App 应依赖的公开 SDK。
SyncBarrier 在 Choreographer 中使用的一个示例
下面是 ViewRootImpl 的关联用法,省略其他状态维护。scheduleTraversals() 插入屏障并登记 traversal 回调;异步 VSync 消息可越过屏障,使 doFrame 有机会执行该回调:
1 | void scheduleTraversals() { |
doTraversal() 在 performTraversals() 之前移除屏障,不是等待 GPU 绘完才移除:
1 | void doTraversal() { |
异步消息只改变跨屏障的可执行性,不能绕过前面已经开始的长任务。不要用私有屏障或任意重排消息的方式修业务卡顿。
厂商优化
系统厂商由于可以直接修改源码,也利用这方面的便利,做一些功能和优化,不过由于保密的问题,代码就不直接放上来了,我可以大概说一下思路,感兴趣的可以私下讨论
移动事件优化
原文“Choreographer 本身没有 input”的说法需要区分:AOSP 已有 CALLBACK_INPUT 和批量输入消费;厂商可能尝试的是改变输入调度或更早响应的策略。本文没有公开对应代码和实验,不能将其当成 AOSP 标准能力。提前处理要同时评估事件合并、重采样、功耗和后续帧的呈现节奏,并非越早越好。
后台动画优化
应用不可见后,自己不断续订的 FrameCallback 或动画可能仍制造工作;是否需要停止,应结合窗口可见性、生命周期、多窗口和具体业务。不能把所有后台回调都判为无意义,也不能仅凭未被杀就推断它能无限制运行。

这张图展示历史 com.netease.newsreader.activity 的密集帧活动。这是我当时记录的后台场景,截图本身没有独立可见性/功耗证据,也不能据此评价当前应用。应用侧先取消不再需要的动画和回调;系统策略还需验证可见窗口、功能正确性与调度限制。
帧绘制优化
“不等 VSync 就立即合成”是原文分享的厂商探索方向,不是普通 App 的公开开关。合成排期仍需满足 Buffer/fence、显示硬件和刷新约束;立即调度不保证降低端到端延迟,也可能增加功耗或破坏 pacing。没有提供实现与对照数据,因此不写成已验证优化。
应用启动优化
启动时应先找关键路径上的长工作、锁和排队。系统内部改变消息顺序要维护生命周期和依赖关系;任意把所谓重要消息放到队首会造成顺序错误或饥饿,不是通用提速方法。普通应用优先减少启动关键路径工作、延后非必要初始化,并核对实际首帧与可交互时间。
高帧率优化
60/90/120Hz 的刷新间隔分别约为 16.67/11.11/8.33ms,原文的 120Hz“8.8ms”不准确。刷新间隔也不是每个线程独占的完整预算。高刷策略需一起评估应用与游戏负载、帧率切换、温度、功耗和呈现节奏,而不能只要求所有 App 永远跑满刷新率。
参考资料
- https://www.jianshu.com/p/304f56f5d486
- http://gityuan.com/2017/02/25/choreographer/
- https://developer.android.com/reference/android/view/Choreographer
- https://www.jishuwen.com/d/2Vcc
- https://juejin.im/entry/5c8772eee51d456cda2e8099
- Android 开发高手课
本文知乎地址
由于博留言交流不方便,点赞或者交流,可以移步本文的知乎界面
知乎 - Android 基于 Choreographer 的渲染机制详解
掘金 - Android 基于 Choreographer 的渲染机制详解
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。
一个人可以走的更快 , 一群人可以走的更远
