【Android】从Choreographer到UI渲染(二)
【Android】从Choreographer到UI渲染(二)
Google 在 2012 年推出的 Project Butter(黄油计划)是 Android 系统发展史上的重要里程碑,旨在解决长期存在的 UI 卡顿、响应延迟等问题,提升用户体验。
在 Android 4.1 之前,系统缺乏统一的帧同步机制,屏幕刷新、应用渲染与用户输入之间常因时序错乱导致画面撕裂(Screen Tearing)或帧丢失(Jank)。
例如,双缓冲机制下,若 GPU 渲染超时,CPU 无法及时处理下一帧,导致后续帧被迫跳过多个刷新周期,用户会明显感知卡顿;触摸事件的处理也因未与屏幕刷新同步,出现“不跟手”的延迟。这些问题严重影响了用户体验,尤其在动画和滚动场景中尤为突出。
为此,Google 通过 VSync 垂直同步、三重缓冲(Triple Buffering) 和 Choreographer 调度框架 三管齐下重构显示系统:VSync 信号将 CPU/GPU 的渲染周期与屏幕刷新严格对齐,确保每帧的准备工作在 16ms(60Hz 屏幕)内启动;三重缓冲通过增加临时缓冲区缓解 GPU 超时导致的连续丢帧问题,牺牲少量内存换取流畅性;而 Choreographer 作为“舞蹈编导”,统一调度输入、动画、绘制等任务,在 VSync 信号到达时批量执行,避免任务碎片化。
垂直同步和三缓冲已经在之前的篇章中提到过,所以今天我们详细分析一下Choregrapher。
本文参考:
Android 之 Choreographer 详细分析 - 简书
何时调用Choreographer
在 Android 的 UI 渲染机制中,无论是 Activity 启动后的首次布局,还是后续通过动画或手动触发的界面更新,最终都会汇聚到 ViewRootImpl 的 scheduleTraversals()
方法。
我们首先来回忆一下Activity的启动与Window的添加过程。
当 Activity 完成 onResume()
后,系统会将其视图层级(DecorView)添加到 Window 中。具体步骤如下:
- Window 创建:Activity 的
Window
(通常是PhoneWindow
)在onCreate()
阶段初始化,并在onResume()
后通过WindowManager
添加到系统。- ViewRootImpl 关联:
WindowManagerGlobal.addView()
方法会创建 ViewRootImpl 实例,并调用其setView()
方法,将 DecorView 与 ViewRootImpl 绑定。- 触发首次布局:在
ViewRootImpl.setView()
中,调用requestLayout()
发起首次测量、布局、绘制请求。
// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {// 关联 DecorViewmView = view;// 请求布局requestLayout();// ... 其他初始化逻辑
}
requestLayout()
是 UI 更新的起点,其核心逻辑如下:
- 标记布局请求:通过
mLayoutRequested
标志位,确保同一帧内多次调用只触发一次布局。- 调度遍历任务:调用
scheduleTraversals()
,安排一次performTraversals()
的执行。
// ViewRootImpl.java
@Override
public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;scheduleTraversals();}
}
scheduleTraversals()
负责协调 UI 更新与 VSync 信号的同步:
- 同步屏障(Sync Barrier):通过
postSyncBarrier()
向主线程的 MessageQueue 插入一个同步屏障,屏蔽普通同步消息,确保 UI 更新任务优先执行。 - 提交回调到 Choreographer:将
mTraversalRunnable
(最终触发doTraversal()
)提交给 Choreographer,在下一个 VSync 信号到达时执行。
//ViewRootImpl.javavoid scheduleTraversals() {if (!mTraversalScheduled) {//此字段保证同时间多次更改只会刷新一次,例如TextView连续两次setText(),也只会走一次绘制流程mTraversalScheduled = true;//添加同步屏障,屏蔽同步消息,保证VSync到来立即执行绘制mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//mTraversalRunnable是TraversalRunnable实例,最终走到run(),也即doTraversal();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;//移除同步屏障mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);...//开始三大绘制流程performTraversals();...}}
Choreographer的创建
它的实例mChoreographer,是在ViewRootImpl的构造方法内使用Choreographer.getInstance()创建:
Choreographer mChoreographer;//ViewRootImpl实例是在添加window时创建
public ViewRootImpl(Context context, Display display) {...mChoreographer = Choreographer.getInstance();...
}
Choreographer
通过 ThreadLocal 实现线程单例,确保每个线程(尤其是主线程)拥有独立的实例。这种设计类似于 Looper
的线程绑定机制,原因如下:
- 线程隔离性:UI 操作必须在主线程执行,
Choreographer
需要与主线程的Looper
绑定,以确保任务调度与消息循环同步。 - 避免竞争:多线程环境下,独立实例可防止任务队列的并发冲突。
// 通过 ThreadLocal 存储每个线程的 Choreographer 实例
private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<Choreographer>() {@Overrideprotected Choreographer initialValue() {// 必须绑定到带有 Looper 的线程Looper looper = Looper.myLooper();if (looper == null) {throw new IllegalStateException("当前线程必须拥有 Looper!");}// 创建实例并与 Looper 关联Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);// 如果是主线程,记录为主实例if (looper == Looper.getMainLooper()) {mMainInstance = choreographer;}return choreographer;}};public static Choreographer getInstance() {return sThreadInstance.get(); // 返回当前线程的实例
}
关键点:
- 强制 Looper 存在:若线程没有
Looper
(如未调用Looper.prepare()
),直接抛出异常。这解释了为什么 UI 操作必须在主线程(主线程默认初始化了Looper
)。 - 主线程标识:通过
Looper.getMainLooper()
判断当前线程是否为主线程,并记录主实例供全局访问。
紧接着是Chroreographer的构造方法:
private Choreographer(Looper looper, int vsyncSource) {mLooper = looper;//使用当前线程looper创建 mHandlermHandler = new FrameHandler(looper);//USE_VSYNC 4.1以上默认是true,表示 具备接受VSync的能力,这个接受能力就是FrameDisplayEventReceivermDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper, vsyncSource): null;mLastFrameTimeNanos = Long.MIN_VALUE;// 计算一帧的时间,Android手机屏幕是60Hz的刷新频率,就是16msmFrameIntervalNanos = (long)(1000000000 / getRefreshRate());// 创建一个链表类型CallbackQueue的数组,大小为5,//也就是数组中有五个链表,每个链表存相同类型的任务:输入、动画、遍历绘制等任务(CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL)mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];for (int i = 0; i <= CALLBACK_LAST; i++) {mCallbackQueues[i] = new CallbackQueue();}// b/68769804: For low FPS experiments.setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));}
Choreographer
的构造函数初始化了多个关键组件,这些组件共同协作完成帧调度:
FrameHandler:异步消息处理器
private final class FrameHandler extends Handler {public FrameHandler(Looper looper) {super(looper); // 绑定当前线程的 Looper}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_DO_FRAME: // 执行帧任务doFrame(System.nanoTime(), 0);break;case MSG_DO_SCHEDULE_VSYNC: // 请求 VSync 信号doScheduleVsync();break;case MSG_DO_SCHEDULE_CALLBACK: // 延迟任务调度doScheduleCallback(msg.arg1);break;}}
}
- 作用:处理与帧调度相关的异步消息(如
MSG_DO_FRAME
),确保任务在主线程执行。 - 异步消息:通过
msg.setAsynchronous(true)
标记消息为异步,绕过同步屏障(后文详述)。
FrameDisplayEventReceiver:VSync 信号接收器
// USE_VSYNC 默认为 true(Android 4.1+ 启用)
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null;
- 功能:通过 JNI 层注册到显示系统,监听硬件 VSync 信号。
- 信号回调:当 VSync 信号到达时,触发
onVsync()
方法,进而通过Choreographer
调度帧任务。
mFrameIntervalNanos:帧间隔时间
// 60Hz 屏幕:1秒 / 60帧 ≈ 16.666ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
- 计算依据:基于屏幕刷新率(如 60Hz)计算每帧的理想时间(16.6ms)。
- 用途:在
doFrame()
中检测帧超时(如判断是否跳帧)。
mCallbackQueues:任务队列数组
// 五种任务类型:输入、动画、插入动画、遍历绘制、提交
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {mCallbackQueues[i] = new CallbackQueue();
}
-
任务分类:将回调任务按类型存入不同队列,确保执行顺序:
CALLBACK_INPUT:处理触摸/输入事件(优先级最高)。
CALLBACK_ANIMATION:执行属性动画、过渡动画。
CALLBACK_INSETS_ANIMATION:窗口插入动画(如状态栏/导航栏变化)。
CALLBACK_TRAVERSAL:执行
View
的测量、布局、绘制。CALLBACK_COMMIT:提交帧数据到渲染线程(最后执行)。
Choreographer调用流程
根据不同的任务类型分派任务
当调用 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, ...)
提交任务时,系统根据任务类型(如 CALLBACK_TRAVERSAL
表示绘制任务)将任务存入对应的 CallbackQueue
队列。
postCallback()内部调用postCallbackDelayed(),接着又调用postCallbackDelayedInternal()
private void postCallbackDelayedInternal(int callbackType, Object action, long delayMillis) {synchronized (mLock) {// 计算任务的到期时间final long dueTime = SystemClock.uptimeMillis() + delayMillis;// 将任务添加到对应类型的队列mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, null);if (dueTime <= now) {scheduleFrameLocked(now); // 立即调度} else {// 发送延迟消息,最终仍触发 scheduleFrameLocked()Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);msg.arg1 = callbackType;msg.setAsynchronous(true); // 关键:异步消息mHandler.sendMessageAtTime(msg, dueTime);}}
}
postCallbackDelayedInternal()
方法会根据延迟时间决定立即调度或延迟处理:若任务需要立即执行,直接调用 scheduleFrameLocked()
申请 VSync 信号;若存在延迟,则通过 FrameHandler
发送异步消息 MSG_DO_SCHEDULE_CALLBACK
,最终仍会触发 scheduleFrameLocked()
。异步消息的标记(msg.setAsynchronous(true)
)是关键设计,它允许这些消息绕过同步屏障——在 ViewRootImpl.scheduleTraversals()
中插入的同步屏障会阻塞普通同步消息,确保 UI 渲染任务优先执行。
private final class FrameHandler extends Handler {public FrameHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_DO_FRAME:// 执行doFrame,即绘制过程doFrame(System.nanoTime(), 0);break;case MSG_DO_SCHEDULE_VSYNC://申请VSYNC信号,例如当前需要绘制任务时doScheduleVsync();break;case MSG_DO_SCHEDULE_CALLBACK://需要延迟的任务,最终还是执行上述两个事件doScheduleCallback(msg.arg1);break;}}}
进第三个case试试。
void doScheduleCallback(int callbackType) {synchronized (mLock) {if (!mFrameScheduled) {final long now = SystemClock.uptimeMillis();if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {scheduleFrameLocked(now);}}}}
发现也是走到这里,即延迟运行最终也会走到scheduleFrameLocked()
private void scheduleFrameLocked(long now) {if (!mFrameScheduled) {mFrameScheduled = true;//开启了VSYNCif (USE_VSYNC) {if (DEBUG_FRAMES) {Log.d(TAG, "Scheduling next frame on vsync.");}//当前执行的线程,是否是mLooper所在线程if (isRunningOnLooperThreadLocked()) {//申请 VSYNC 信号scheduleVsyncLocked();} else {// 若不在,就用mHandler发送消息到原线程,最后还是调用scheduleVsyncLocked方法Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);msg.setAsynchronous(true);//异步mHandler.sendMessageAtFrontOfQueue(msg);}} else {// 如果未开启VSYNC则直接doFrame方法(4.1后默认开启)final long nextFrameTime = Math.max(mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);if (DEBUG_FRAMES) {Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");}Message msg = mHandler.obtainMessage(MSG_DO_FRAME);msg.setAsynchronous(true);//异步mHandler.sendMessageAtTime(msg, nextFrameTime);}}}
FrameHandler的作用很明显里了:发送异步消息(因为前面设置了同步屏障)。有延迟的任务发延迟消息、不在原线程的发到原线程、没开启VSYNC的直接走 doFrame 方法取执行绘制。
申请与接受VSync
Choreographer
会通过 scheduleVsyncLocked()
方法向系统申请 VSync 信号。
private void scheduleVsyncLocked() {mDisplayEventReceiver.scheduleVsync();}
这一过程的核心在于 FrameDisplayEventReceiver
,也就是此处的mDisplayEventReceiver,它是 DisplayEventReceiver
的子类,在构造时通过 JNI 层与底层显示服务建立连接(nativeInit
),注册为 VSync 信号的监听者。当调用 scheduleVsync()
时,实际通过 nativeScheduleVsync
这一本地方法向系统请求下一次 VSync 中断信号。一旦硬件产生 VSync 脉冲(例如屏幕准备刷新下一帧时),系统会回调 FrameDisplayEventReceiver
的 onVsync
方法,此时该方法将当前 VSync 的时间戳、显示设备 ID 和帧序号记录下来,并将自身封装为一个异步消息(msg.setAsynchronous(true)
)发送到主线程的 MessageQueue
。
接下来看看代码怎么说:
public DisplayEventReceiver(Looper looper, int vsyncSource) {if (looper == null) {throw new IllegalArgumentException("looper must not be null");}mMessageQueue = looper.getQueue();// 注册VSYNC信号监听者mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,vsyncSource);mCloseGuard.open("dispose");}
在 DisplayEventReceiver 的构造方法会通过 JNI 创建一个 IDisplayEventConnection 的 VSYNC 的监听者。
FrameDisplayEventReceiver的scheduleVsync()就是在 DisplayEventReceiver中:
public void scheduleVsync() {if (mReceiverPtr == 0) {Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event " + "receiver has already been disposed.");} else {// 申请VSYNC中断信号,会回调onVsync方法nativeScheduleVsync(mReceiverPtr);}}
当调用 scheduleVsync()
时,实际通过 nativeScheduleVsync
这一本地方法向系统请求下一次 VSync 中断信号。
一旦硬件产生 VSync 脉冲(例如屏幕准备刷新下一帧时),系统会回调 FrameDisplayEventReceiver
的 onVsync
方法,此时该方法将当前 VSync 的时间戳、显示设备 ID 和帧序号记录下来,并将自身封装为一个异步消息(msg.setAsynchronous(true)
)发送到主线程的 MessageQueue
。
/*** 接收到VSync脉冲时 回调* @param timestampNanos VSync脉冲的时间戳* @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.* @param frame 帧号码,自增*/@UnsupportedAppUsagepublic void onVsync(long timestampNanos, long physicalDisplayId, int frame) {}
具体实现是在FrameDisplayEventReceiver中:
private final class FrameDisplayEventReceiver extends DisplayEventReceiverimplements Runnable {private boolean mHavePendingVsync;private long mTimestampNanos;private int mFrame;public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {super(looper, vsyncSource);}@Overridepublic void onVsync(long timestampNanos, long physicalDisplayId, int frame) {// Post the vsync event to the Handler.// The idea is to prevent incoming vsync events from completely starving// the message queue. If there are no messages in the queue with timestamps// earlier than the frame time, then the vsync event will be processed immediately.// Otherwise, messages that predate the vsync event will be handled first.long now = System.nanoTime();if (timestampNanos > now) {Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)+ " ms in the future! Check that graphics HAL is generating vsync "+ "timestamps using the correct timebase.");timestampNanos = now;}if (mHavePendingVsync) {Log.w(TAG, "Already have a pending vsync event. There should only be "+ "one at a time.");} else {mHavePendingVsync = true;}mTimestampNanos = timestampNanos;mFrame = frame;//将本身作为runnable传入msg, 发消息后 会走run(),即doFrame(),也是异步消息Message msg = Message.obtain(mHandler, this);msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);}@Overridepublic void run() {mHavePendingVsync = false;doFrame(mTimestampNanos, mFrame);}}
当主线程处理到这条异步消息时,会执行 FrameDisplayEventReceiver
的 run()
方法,进而调用 Choreographer.doFrame()
,这是整个渲染流程的起点。
doFrame执行过程
void doFrame(long frameTimeNanos, int frame) {final long startNanos;synchronized (mLock) {if (!mFrameScheduled) {return; // no work to do}...// 预期执行时间long intendedFrameTimeNanos = frameTimeNanos;startNanos = System.nanoTime();// 超时时间是否超过一帧的时间(这是因为MessageQueue虽然添加了同步屏障,但是还是有正在执行的同步任务,导致doFrame延迟执行了)final long jitterNanos = startNanos - frameTimeNanos;if (jitterNanos >= mFrameIntervalNanos) {// 计算掉帧数final long skippedFrames = jitterNanos / mFrameIntervalNanos;if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {// 掉帧超过30帧打印Log提示Log.i(TAG, "Skipped " + skippedFrames + " frames! "+ "The application may be doing too much work on its main thread.");}final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;...frameTimeNanos = startNanos - lastFrameOffset;}...mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);// Frame标志位恢复mFrameScheduled = false;// 记录最后一帧时间mLastFrameTimeNanos = frameTimeNanos;}try {// 按类型顺序 执行任务Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);mFrameInfo.markInputHandlingStart();doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);mFrameInfo.markAnimationsStart();doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);mFrameInfo.markPerformTraversalsStart();doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);} finally {AnimationUtils.unlockAnimationClock();Trace.traceEnd(Trace.TRACE_TAG_VIEW);}}
在 doFrame()
中,系统首先通过计算当前时间与 VSync 信号时间戳的差值(jitterNanos = startNanos - frameTimeNanos
)判断是否发生跳帧:若差值超过一帧的理论时长(如 16.6ms 对应 60Hz 屏幕),则按超出时长除以单帧时间计算跳帧数,若超过 30 帧(约 500ms)则打印警告日志,提示主线程可能存在耗时操作。
随后,系统按固定顺序处理任务队列——依次执行输入事件(CALLBACK_INPUT
)、动画更新(CALLBACK_ANIMATION
)、窗口插入动画(CALLBACK_INSETS_ANIMATION
)、视图树遍历(CALLBACK_TRAVERSAL
)和帧提交(CALLBACK_COMMIT
)。
每个队列的任务通过 doCallbacks()
方法提取并执行:例如 CALLBACK_TRAVERSAL
队列中的任务通常是 ViewRootImpl
提交的 mTraversalRunnable
,其 run()
方法最终触发 performTraversals()
,执行测量、布局、绘制三大流程。
void doCallbacks(int callbackType, long frameTimeNanos) {CallbackRecord callbacks;synchronized (mLock) {final long now = System.nanoTime();// 根据指定的类型CallbackkQueue中查找到达执行时间的CallbackRecordcallbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);if (callbacks == null) {return;}mCallbacksRunning = true;//提交任务类型if (callbackType == Choreographer.CALLBACK_COMMIT) {final long jitterNanos = now - frameTimeNanos;if (jitterNanos >= 2 * mFrameIntervalNanos) {final long lastFrameOffset = jitterNanos % mFrameIntervalNanos+ mFrameIntervalNanos;if (DEBUG_JANK) {Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)+ " ms which is more than twice the frame interval of "+ (mFrameIntervalNanos * 0.000001f) + " ms! "+ "Setting frame time to " + (lastFrameOffset * 0.000001f)+ " ms in the past.");mDebugPrintNextFrameTimeDelta = true;}frameTimeNanos = now - lastFrameOffset;mLastFrameTimeNanos = frameTimeNanos;}}}try {// 迭代执行队列所有任务for (CallbackRecord c = callbacks; c != null; c = c.next) {// 回调CallbackRecord的run,其内部回调Callback的runc.run(frameTimeNanos);}} finally {synchronized (mLock) {mCallbacksRunning = false;do {final CallbackRecord next = callbacks.next;//回收CallbackRecordrecycleCallbackLocked(callbacks);callbacks = next;} while (callbacks != null);}}}
任务的具体执行由 CallbackRecord.run()
完成,该函数根据 token
判断任务类型——若 token
为 FRAME_CALLBACK_TOKEN
(通过 postFrameCallback()
提交),则调用 FrameCallback.doFrame()
接口;否则直接执行 Runnable.run()
。
private static final class CallbackRecord {public CallbackRecord next;public long dueTime;public Object action; // Runnable or FrameCallbackpublic Object token;@UnsupportedAppUsagepublic void run(long frameTimeNanos) {if (token == FRAME_CALLBACK_TOKEN) {// 通过postFrameCallback 或 postFrameCallbackDelayed,会执行这里((FrameCallback)action).doFrame(frameTimeNanos);} else {//取出Runnable执行run()((Runnable)action).run();}}}
前面看到mChoreographer.postCallback传的token是null,所以取出action,就是Runnable,执行run(),这里的action就是 ViewRootImpl 发起的绘制任务mTraversalRunnable了,那么这样整个逻辑就闭环了。
那么 啥时候 token == FRAME_CALLBACK_TOKEN 呢?答案是Choreographer的postFrameCallback()方法:
public void postFrameCallback(FrameCallback callback) {postFrameCallbackDelayed(callback, 0);}public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {if (callback == null) {throw new IllegalArgumentException("callback must not be null");}//也是走到是postCallbackDelayedInternal,并且注意是CALLBACK_ANIMATION类型,//token是FRAME_CALLBACK_TOKEN,action就是FrameCallbackpostCallbackDelayedInternal(CALLBACK_ANIMATION,callback, FRAME_CALLBACK_TOKEN, delayMillis);}public interface FrameCallback {public void doFrame(long frameTimeNanos);}
计算丢帧
举个栗子:
//Application.javapublic void onCreate() {super.onCreate();//在Application中使用postFrameCallbackChoreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));}public class FPSFrameCallback implements Choreographer.FrameCallback {private static final String TAG = "FPS_TEST";private long mLastFrameTimeNanos = 0;private long mFrameIntervalNanos;public FPSFrameCallback(long lastFrameTimeNanos) {mLastFrameTimeNanos = lastFrameTimeNanos;mFrameIntervalNanos = (long)(1000000000 / 60.0);}@Overridepublic void doFrame(long frameTimeNanos) {//初始化时间if (mLastFrameTimeNanos == 0) {mLastFrameTimeNanos = frameTimeNanos;}final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;if (jitterNanos >= mFrameIntervalNanos) {final long skippedFrames = jitterNanos / mFrameIntervalNanos;if(skippedFrames>30){//丢帧30以上打印日志Log.i(TAG, "Skipped " + skippedFrames + " frames! "+ "The application may be doing too much work on its main thread.");}}mLastFrameTimeNanos=frameTimeNanos;//注册下一帧回调Choreographer.getInstance().postFrameCallback(this);}}
通过 postFrameCallback()
注册一个 FrameCallback
,在每次 doFrame()
时计算相邻两次 VSync 信号的时间差,若超过阈值则判定为跳帧。例如,示例中的 FPSFrameCallback
在每次回调时比较当前帧与上一帧的时间差,若超过 16.6ms 的倍数,则累加跳帧数并打印警告。
这种设计使得所有 UI 操作(无论是触摸、动画还是绘制)都被严格对齐到 VSync 信号,既避免了画面撕裂,又为每一帧提供了完整的处理窗口,而开发者可通过监听回调精准定位主线程卡顿的根源,例如在 doFrame
中插入性能埋点或结合 Systrace
工具分析耗时任务。