【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。
本文参考:
何时调用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()
发起首次测量、布局、绘制请求。
java
// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// 关联 DecorView
mView = view;
// 请求布局
requestLayout();
// ... 其他初始化逻辑
}
requestLayout()
是 UI 更新的起点,其核心逻辑如下:
- 标记布局请求 :通过
mLayoutRequested
标志位,确保同一帧内多次调用只触发一次布局。- 调度遍历任务 :调用
scheduleTraversals()
,安排一次performTraversals()
的执行。
java
// 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 信号到达时执行。
java
//ViewRootImpl.java
void 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 {
@Override
public 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()创建:
java
Choreographer mChoreographer;
//ViewRootImpl实例是在添加window时创建
public ViewRootImpl(Context context, Display display) {
...
mChoreographer = Choreographer.getInstance();
...
}
Choreographer
通过 ThreadLocal 实现线程单例,确保每个线程(尤其是主线程)拥有独立的实例。这种设计类似于 Looper
的线程绑定机制,原因如下:
- 线程隔离性 :UI 操作必须在主线程执行,
Choreographer
需要与主线程的Looper
绑定,以确保任务调度与消息循环同步。 - 避免竞争:多线程环境下,独立实例可防止任务队列的并发冲突。
java
// 通过 ThreadLocal 存储每个线程的 Choreographer 实例
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected 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的构造方法:
java
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
//使用当前线程looper创建 mHandler
mHandler = new FrameHandler(looper);
//USE_VSYNC 4.1以上默认是true,表示 具备接受VSync的能力,这个接受能力就是FrameDisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
// 计算一帧的时间,Android手机屏幕是60Hz的刷新频率,就是16ms
mFrameIntervalNanos = (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:异步消息处理器
java
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper); // 绑定当前线程的 Looper
}
@Override
public 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 信号接收器
java
// USE_VSYNC 默认为 true(Android 4.1+ 启用)
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
- 功能:通过 JNI 层注册到显示系统,监听硬件 VSync 信号。
- 信号回调 :当 VSync 信号到达时,触发
onVsync()
方法,进而通过Choreographer
调度帧任务。
mFrameIntervalNanos:帧间隔时间
java
// 60Hz 屏幕:1秒 / 60帧 ≈ 16.666ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
- 计算依据:基于屏幕刷新率(如 60Hz)计算每帧的理想时间(16.6ms)。
- 用途 :在
doFrame()
中检测帧超时(如判断是否跳帧)。
mCallbackQueues:任务队列数组
java
// 五种任务类型:输入、动画、插入动画、遍历绘制、提交
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()
java
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 渲染任务优先执行。
java
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,即绘制过程
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
//申请VSYNC信号,例如当前需要绘制任务时
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
//需要延迟的任务,最终还是执行上述两个事件
doScheduleCallback(msg.arg1);
break;
}
}
}
进第三个case试试。
java
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}
发现也是走到这里,即延迟运行最终也会走到scheduleFrameLocked()
java
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
//开启了VSYNC
if (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 信号。
java
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
这一过程的核心在于 FrameDisplayEventReceiver
,也就是此处的mDisplayEventReceiver,它是 DisplayEventReceiver
的子类,在构造时通过 JNI 层与底层显示服务建立连接(nativeInit
),注册为 VSync 信号的监听者。当调用 scheduleVsync()
时,实际通过 nativeScheduleVsync
这一本地方法向系统请求下一次 VSync 中断信号。一旦硬件产生 VSync 脉冲(例如屏幕准备刷新下一帧时),系统会回调 FrameDisplayEventReceiver
的 onVsync
方法,此时该方法将当前 VSync 的时间戳、显示设备 ID 和帧序号记录下来,并将自身封装为一个异步消息(msg.setAsynchronous(true)
)发送到主线程的 MessageQueue
。
接下来看看代码怎么说:
java
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中:
java
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
。
java
/**
* 接收到VSync脉冲时 回调
* @param timestampNanos VSync脉冲的时间戳
* @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
* @param frame 帧号码,自增
*/
@UnsupportedAppUsage
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
}
具体实现是在FrameDisplayEventReceiver中:
java
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public 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);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
当主线程处理到这条异步消息时,会执行 FrameDisplayEventReceiver
的 run()
方法,进而调用 Choreographer.doFrame()
,这是整个渲染流程的起点。
doFrame执行过程
java
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()
,执行测量、布局、绘制三大流程。
java
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 根据指定的类型CallbackkQueue中查找到达执行时间的CallbackRecord
callbacks = 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的run
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
//回收CallbackRecord
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
}
}
任务的具体执行由 CallbackRecord.run()
完成,该函数根据 token
判断任务类型------若 token
为 FRAME_CALLBACK_TOKEN
(通过 postFrameCallback()
提交),则调用 FrameCallback.doFrame()
接口;否则直接执行 Runnable.run()
。
java
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
@UnsupportedAppUsage
public 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()方法:
java
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就是FrameCallback
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
public interface FrameCallback {
public void doFrame(long frameTimeNanos);
}
计算丢帧
举个栗子:
java
//Application.java
public void onCreate() {
super.onCreate();
//在Application中使用postFrameCallback
Choreographer.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);
}
@Override
public 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
工具分析耗时任务。
放一个流程图~
