FrameHandler 帧调度
Choreographer 的 FrameHandler 不是一个“每 16 ms 自动运行的 Handler”。在 Android 17 中,它负责消费三类内部消息:执行无 VSYNC 模式的 frame、从 Looper 线程调用 scheduleVsync()、以及让延迟 callback 到期后重新申请 frame。真实的显示 VSYNC 由 FrameDisplayEventReceiver 接收,再把事件封装成异步 Message 投递给同一个 Handler。
本文面向已经读过 ViewRootHandler 绘制路径、 异步 Handler 工厂、消息延迟边界 和 异步消息与 VSYNC 的读者。本文聚焦 Choreographer.java 内部的消息和回调编排:Looper、FrameHandler、DisplayEventReceiver、 CallbackQueue 和 doFrame() 如何分工;不把 SurfaceFlinger 的 VSYNC 产生机制或完整绘制 渲染器实现塞入本文。
读完后,读者应能定位 VSYNC 事件何时进入 Handler,解释为什么消息要标记异步,判断延迟 callback 为什么先进入 CallbackQueue 而不是直接执行,并从 doFrame() 的状态检查、跳帧和 回调回收路径理解一帧没有执行、推迟或异常时的边界。
1. 实例归属
源码文件:frameworks/base/core/java/android/view/Choreographer.java
每个已有 Looper 的线程通过 ThreadLocal 获取自己的 Choreographer。构造函数保存 Looper, 创建绑定该 Looper 的 FrameHandler 和 FrameDisplayEventReceiver,再创建五个回调队列。
private Choreographer(Looper looper, long layerHandle) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, layerHandle)
: null;
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}FrameHandler 的线程归属来自构造参数的 Looper;FrameDisplayEventReceiver 也绑定同一 Looper。它们不创建 worker 线程,VSYNC 到达后的 Java doFrame() 仍由该 Looper 线程执行。
| 对象 | 所有者 | 读取/调用者 | 状态 |
|---|---|---|---|
mHandler | Choreographer | FrameDisplayEventReceiver、延迟 callback | 消费内部 Message |
mDisplayEventReceiver | Choreographer | native display event 入口 | 接收 VSYNC |
mCallbackQueues | Choreographer | postCallback、doCallbacks | 保存按阶段的 CallbackRecord |
mFrameScheduled | Choreographer | scheduleFrameLocked、doFrame | 是否已有待处理帧 |
2. 内部消息
源码文件:frameworks/base/core/java/android/view/Choreographer.java
FrameHandler 只处理三个 what:MSG_DO_FRAME、MSG_DO_SCHEDULE_VSYNC、 MSG_DO_SCHEDULE_CALLBACK。它不是公开业务 Handler;每个 case 都调用 Choreographer 的内部 方法。
private static final int MSG_DO_FRAME = 0;
private static final int MSG_DO_SCHEDULE_VSYNC = 1;
private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0,
new DisplayEventReceiver.VsyncEventData());
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}MSG_DO_FRAME 是无 VSYNC fallback 或特殊模式的入口;正常 VSYNC 会把 FrameDisplayEventReceiver 本身作为 Runnable 放进 Message.callback,不一定使用这个 what。所以不能仅凭三个常量概括所有 frame 入口。
3. 回调阶段
源码文件:frameworks/base/core/java/android/view/Choreographer.java
Choreographer 为五个阶段建立独立 CallbackQueue:INPUT、ANIMATION、 INSETS_ANIMATION、TRAVERSAL、COMMIT。阶段编号被 doFrame() 以固定顺序调用,但每个 队列内部仍按 dueTime 排序。
public static final int CALLBACK_INPUT = 0;
public static final int CALLBACK_ANIMATION = 1;
public static final int CALLBACK_INSETS_ANIMATION = 2;
public static final int CALLBACK_TRAVERSAL = 3;
public static final int CALLBACK_COMMIT = 4;
private final CallbackQueue[] mCallbackQueues =
new CallbackQueue[CALLBACK_LAST + 1];阶段顺序是 Choreographer 的内部调用顺序,不是 Handler 消息优先级。所有阶段都由同一个 Looper 线程串行执行;消息屏障只影响 VSYNC Message 何时进入 Looper,不会让阶段并行。
4. 回调入队
源码文件:frameworks/base/core/java/android/view/Choreographer.java
postCallbackDelayed() 先校验 action 和阶段,再在 mLock 内计算 dueTime,把动作放入 对应 CallbackQueue。如果动作已经到期,立即请求 frame;如果是未来时间,发送异步的 MSG_DO_SCHEDULE_CALLBACK,由它在到期后检查并请求 frame。
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}延迟 Message 不承载真正的 callback 执行,而是承载“到期后是否需要申请 frame”的提示。 真正的 action 仍在 CallbackQueue 中,等 doFrame() 对应阶段提取。
这个分层解释了延迟帧 callback 的实际语义:延迟到期只让 callback 有资格在下一帧被提取, 不保证 action 在 dueTime 的同一毫秒开始执行。
5. 消息入口
源码文件:frameworks/base/core/java/android/view/Choreographer.java
当 scheduleFrameLocked() 在非 Looper 线程被调用时,它不能直接操作 DisplayEventReceiver,于是发送异步的 MSG_DO_SCHEDULE_VSYNC 到 FrameHandler;Looper 线程 取到后再调用 doScheduleVsync()。如果已经在 Looper 线程,则直接申请 VSYNC。
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}mFrameScheduled 在这里承担去重:多次 post callback 只申请一个尚未消费的 frame。异步位让 内部调度消息不受 ViewRootImpl traversal barrier 阻挡,但不改变它们仍需在 Choreographer Looper 上串行执行的事实。
6. VSYNC 转发
源码文件:frameworks/base/core/java/android/view/Choreographer.java
FrameDisplayEventReceiver.onVsync() 收到 native 事件后,保存时间戳、frame 编号和 VsyncEventData,再把自己作为 Runnable 封装进 Message。消息的时间使用 VSYNC 时间戳转换 后的 uptime 时间,并显式设置异步位。
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
long now = System.nanoTime();
if (timestampNanos > now) {
timestampNanos = now;
}
if (mHavePendingVsync) {
// 记录重复 pending 的异常情况
} else {
mHavePendingVsync = true;
}
mTimestampNanos = timestampNanos;
mFrame = frame;
mLastVsyncEventData.copyFrom(vsyncEventData);
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}onVsync() 可能来自 native 显示事件线程,但 run() 在 Handler 绑定的 Looper 线程执行。 mHavePendingVsync 和时间字段的写入配合“事件只允许一个 pending”逻辑,防止连续 VSYNC 事件无界堆积。
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}这里的 Runnable 身份很关键:Handler 分发时会走 Message.callback.run(),而不是进入 FrameHandler.handleMessage(MSG_DO_FRAME)。因此正常 VSYNC 和 fallback frame 是两条不同的 消息入口,但最终都汇聚到 doFrame()。
7. 帧状态
源码文件:frameworks/base/core/java/android/view/Choreographer.java
doFrame() 首先根据 buffer stuffing 状态决定是否延迟一个 frame,再在锁内检查 mFrameScheduled。如果没有待处理 frame,方法记录后直接返回;如果 frame 时间倒退或 FPS divisor 要求跳过,也会重新申请 VSYNC 后返回。
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
switch (updateBufferStuffingState(frameTimeNanos, vsyncEventData)) {
case DELAY_FRAME:
mBufferStuffingState.numberWaitsForNextVsync++;
mBufferStuffingState.accumulatedDelayNanos += vsyncEventData.frameInterval;
scheduleVsyncLocked();
return;
default:
break;
}
synchronized (mLock) {
if (!mFrameScheduled) {
traceMessage("Frame not scheduled");
return;
}
// 计算 jitter、更新 frame data,并把 mFrameScheduled 清零
}
}mFrameScheduled 清零发生在确认 frame 可执行之后。它不是“VSYNC 已到达”标志,而是 “当前 Choreographer 是否仍有 callback 工作需要消费”的状态。VSYNC 到达但 callback 已被 移除时,doFrame() 可以无工作返回。
8. 跳帧判断
源码文件:frameworks/base/core/java/android/view/Choreographer.java
doFrame() 用当前 System.nanoTime() 与原始 VSYNC 时间计算 jitter。如果晚于一个 frame interval,就调整 frame time;跳过的帧数达到 SKIPPED_FRAME_WARNING_LIMIT 时输出警告。
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= frameIntervalNanos) {
final long skippedFrames = jitterNanos / frameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
// 对 frameTimeNanos 重同步
}这个分支说明 FrameHandler 不能保证准时:Message 到达 Looper 后,主线程可能已经错过 VSYNC。日志是对 jitter 的诊断,不是对某个具体 View 或 callback 的根因证明;要定位根因仍 需结合各阶段 callback、Looper 日志和主线程栈。
9. 阶段执行
源码文件:frameworks/base/core/java/android/view/Choreographer.java
一帧确认可执行后,doFrame() 按阶段调用 doCallbacks():输入、动画、insets 动画、 traversal、commit。阶段间使用同一个 mFrameData 和动画时钟;没有 callback 的阶段会直接 返回。
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(CALLBACK_INPUT);
mFrameInfo.markAnimationsStart();
doCallbacks(CALLBACK_ANIMATION);
doCallbacks(CALLBACK_INSETS_ANIMATION);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(CALLBACK_TRAVERSAL);
doCallbacks(CALLBACK_COMMIT);图中 CallbackQueue 是 callback 的真正 owner;FrameHandler 只负责把 frame 入口交给 Choreographer。阶段顺序不是五条 Handler 消息,而是同一次 doFrame() 中的五次队列提取。
10. 队列提取
源码文件:frameworks/base/core/java/android/view/Choreographer.java
CallbackQueue 是按 dueTime 排序的单链表。extractDueCallbacksLocked() 一次摘下所有 已到期记录,把第一条未到期记录留在队头;之后 doCallbacks() 在锁外运行这些 action。
public CallbackRecord extractDueCallbacksLocked(long now) {
CallbackRecord callbacks = mHead;
if (callbacks == null || callbacks.dueTime > now) {
return null;
}
CallbackRecord last = callbacks;
CallbackRecord next = last.next;
while (next != null) {
if (next.dueTime > now) {
last.next = null;
break;
}
last = next;
next = next.next;
}
mHead = next;
return callbacks;
}回调执行期间可以再向同一阶段或后续阶段注册 callback;因为提取已经在锁内完成,新增记录 不会插入当前链表片段。doCallbacks() 完成后,finally 会把已执行记录逐个回收到 callback pool,避免长期持有 action 引用。
try {
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(mFrameData);
}
} finally {
synchronized (mLock) {
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
}11. 回调异常
源码文件:frameworks/base/core/java/android/view/Choreographer.java
doCallbacks() 的 finally 负责回收当前已经提取的 CallbackRecord,但它没有在循环内捕获 action 抛出的任意异常。一个 action 抛异常时,后续同一阶段的 action 不会从该循环继续执行, 但已提取记录仍会进入 finally 回收。
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(mFrameData);
}
} finally {
// mCallbacksRunning=false,并回收 callbacks 链
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}这与 FrameHandler.handleMessage() 的三路分发不同:FrameHandler 不为 doFrame() 建立 Future,也不把 callback 异常转换成普通结果。最终异常仍会进入 Looper 的线程级异常处理, 不能把“CallbackRecord 已回收”理解成“帧已经成功完成”。
12. 回调取消
源码文件:frameworks/base/core/java/android/view/Choreographer.java
取消分为两层:先从 CallbackQueue 删除匹配 action/token,再在 action 非空且 token 为空时, 移除对应的 MSG_DO_SCHEDULE_CALLBACK。这样未来 callback 的提示消息和真正 callback 记录 可以同时被取消。
private void removeCallbacksInternal(int callbackType, Object action, Object token) {
synchronized (mLock) {
mCallbackQueues[callbackType].removeCallbacksLocked(action, token);
if (action != null && token == null) {
mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action);
}
}
}
public void removeFrameCallback(FrameCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
}如果 doCallbacks() 已经把记录摘出,后续 remove 不能撤回正在运行的 action;取消只覆盖 仍在 CallbackQueue 或延迟调度 Message 中的工作。这与 Handler removeCallbacks() 的出队 竞态相似,但 Choreographer 还多了一层自己的 CallbackRecord 队列。
13. 无 VSYNC 路径
源码文件:frameworks/base/core/java/android/view/Choreographer.java
当 USE_VSYNC 为 false,scheduleFrameLocked() 发送异步 MSG_DO_FRAME,在计算出的下一 帧时间调用 FrameHandler.handleMessage();此时不会通过 DisplayEventReceiver 接收真实 VSYNC,但仍使用同一个 doFrame() 和 callback 阶段。
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);因此 FrameHandler 不是“只处理 VSYNC 回调”的类;它是 Choreographer 的 Looper 入口,既 消费真实显示事件转发,也消费无 VSYNC fallback。两条路径在 doFrame() 汇合后才共享帧状态 和阶段队列。
14. 延迟阶段
源码文件:frameworks/base/core/java/android/view/Choreographer.java
到期的 MSG_DO_SCHEDULE_CALLBACK 只在尚未有 frame 预约时检查对应队列;如果有到期 callback, 调用 scheduleFrameLocked(now)。如果 frame 已经预约,消息不会重复申请第二帧。
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}这条路径解释了“延迟 callback 到期但没有立刻运行”的原因:到期只触发 frame 申请,action 还需等 VSYNC 或 fallback frame 的对应阶段。dueTime 是 callback 的资格时间,不是 FrameCallback.doFrame() 的开始时间。
15. 时间与数据
源码文件:frameworks/base/core/java/android/view/Choreographer.java
VSYNC 事件同时携带纳秒时间戳和 VsyncEventData。doFrame() 会计算 jitter、更新 FrameData,并在 callback 中提供 frame time、vsync id 和 deadline 等数据。数据的有效范围 由 callback 契约限定,不应在 callback 之外持有引用推断未来帧。
public interface VsyncCallback {
void onVsync(@NonNull FrameData data);
}FrameData 的时间线选择和 buffer stuffing recovery 会影响 doFrame() 使用的 frame time; 这不是简单的“收到 timestamp 就原样传给所有回调”。阅读性能问题时,应区分原始 VSYNC 时间、 重同步后的 frame time 和 callback 实际开始时间。
16. 验证输入
源码文件:frameworks/base/core/java/android/view/Choreographer.java
第一组验证是 callback 阶段和取消:向指定 callback type 加入一个 action,调用 removeCallbacks(type, action, token),再检查该 action 不会从对应 CallbackQueue 提取。 同时应检查延迟 callback 的 MSG_DO_SCHEDULE_CALLBACK 被按 action 移除。
choreographer.postCallbackDelayed(
Choreographer.CALLBACK_ANIMATION, action, token, 1000);
choreographer.removeCallbacks(
Choreographer.CALLBACK_ANIMATION, action, token);这组输入证明的是 Choreographer 自己的记录和提示消息取消,不证明已经执行的 callback 会被 中断。
第二组验证是帧去重:连续 post 两个 callback,确认 mFrameScheduled 只导致一次 frame 申请,而 doFrame() 在对应阶段提取两个到期记录。它证明的是预约合并,不证明两个 callback 会在相同耗时或相同硬件帧时间完成。
第三组验证是异常回收:让一个 callback 抛出异常,观察 doCallbacks() 的 finally 是否将 已提取 CallbackRecord 回收到 pool,并确认外层 Looper 的异常策略仍然可见。没有设备 VSYNC 时,可用 USE_VSYNC=false 的测试配置验证 MSG_DO_FRAME fallback,但不能把 fallback 时间精度外推到真实显示刷新率。
17. 复查命令
源码文件:
frameworks/base/core/java/android/view/Choreographer.javaframeworks/base/core/java/android/view/ViewRootImpl.javaframeworks/base/core/java/android/os/Handler.javaframeworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
rg -n "class FrameHandler|MSG_DO_FRAME|MSG_DO_SCHEDULE_VSYNC|MSG_DO_SCHEDULE_CALLBACK" \
frameworks/base/core/java/android/view/Choreographer.java
rg -n "postCallbackDelayedInternal|scheduleFrameLocked|doScheduleVsync|doScheduleCallback" \
frameworks/base/core/java/android/view/Choreographer.java
rg -n "FrameDisplayEventReceiver|onVsync|setAsynchronous|doFrame" \
frameworks/base/core/java/android/view/Choreographer.java
rg -n "doCallbacks|extractDueCallbacksLocked|removeCallbacksInternal|mFrameScheduled" \
frameworks/base/core/java/android/view/Choreographer.java阅读一帧时应按“callback 入队 → frame 预约 → VSYNC Message → doFrame 状态检查 → 阶段提取 → record 回收”的顺序走源码。把 postCallback 直接等同于 Runnable 入 Handler 队列, 会漏掉 CallbackQueue 的 dueTime、阶段和取消语义。
18. 使用边界
Choreographer 的 FrameHandler 适合把输入、动画、insets、traversal 和 commit callback 组织到显示帧阶段。它不提供通用延迟执行器,不保证 callback 在 dueTime 精确开始,不会让 主线程工作并行,也不会在 callback 抛异常后继续执行同一阶段剩余 action。
当出现掉帧或 callback 未执行时,按源码检查:消息是否进入正确 Looper,mFrameScheduled 是否被提前清除,VSYNC 是否已转成异步 Message,CallbackQueue 的 dueTime 是否到期,callback 是否在 doCallbacks() 已被摘出,以及 doFrame() 是否因 jitter、时间倒退或 buffer recovery 重新申请了下一帧。这样才能区分预约缺失、消息等待、阶段未到、取消成功和主线程过载。
