Choreographer
简介
Choreographer
通过内部的 FrameDisplayEventReceiver
来接收 Vsync
(是由 SurfaceFlinger
下发的) 信号,接收到 Vsync
后统一处理 InputEvent
(部分事件由这里处理,下发至 ViewRootImpl
)、Animation
(如果是调用的 View#postOnAnimation
方法,就会直接回调给对应的 Runnable
) 和 Traversal
(也就是对应的 View 的 layout, measure 和 draw 流程,事件是下发至 ViewRootImpl
,然后通过冒泡的方式依次下发至所有的 View)等任务。
这里以 View 的绘制为例子来描述一下这个过程:假如 View 的 UI 需要修改,我们都会调用 View#invalidate() 方法请求重新绘制,这个方法会异常调用 parent 的 invalidate 方法,最后会调用到 ViewRootImpl
中的 invalidate 方法,在 ViewRootImpl
中会直接向 Choreographer
中请求添加一个 Traversal
类型的任务,在下次的 Vsync
信号来了后就会执行这个任务(如果这时还有别的任务也会执行),最后完成绘制。
在 Activity
进行 resume
的生命周期时会创建 ViewRootImpl
实例 (别的和 UI 相关的地方也有可能创建,例如 Dialog
.),ViewRootImpl
的构造函数中会去获取 Choreographer
实例,这是一个单例对象。
后续的代码阅读都是基于 API 31
获取 Vsync
在 Choreographer
的构造函数中会初始化 FrameDisplayEventReceiver
实例,来用于获取 Vsync
信号,其中 onVsync
方法就是垂直同步的信号回调,其中该方法中包含了帧的一些重要信息,包括 Vsync 信号的时间戳、帧序号、帧间隔、帧绘制完成的 Deadline:
Java
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
private VsyncEventData mLastVsyncEventData = new VsyncEventData();
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource, 0);
}
// TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
// the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
// for the internal display implicitly.
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#onVsync " + vsyncEventData.id);
}
// 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;
mLastVsyncEventData = vsyncEventData;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
}
在 onVsync
回调方法中首先检查信号的时间戳如果大于当前的时间丢弃当前信号,如果上次的 Vsync
信号还没有处理完也直接丢弃。然后通过 handler (在初始化的时候创建,工作在主线程) 将其加入到任务队列中,注意这里的 msg 是异步的(处理优先级更高),最后任务的处理是在 run
方法中,处理的入口函数是 doFrame
方法,这里还有一点要提醒一下 onVsync
信号回调不是每次都会调用的,需要手动调用 FrameDisplayEventReceiver#scheduleVsync()
方法后才会把下次的 Vsync
信号回调给我们。
执行任务
在讲执行任务之前,我们先讲一讲这些等待执行的任务是放在哪里的,怎么放的。他们都是按照分类放在 private final CallbackQueue[] mCallbackQueues;
中,这个数组中依次放了 InputEvent
(CALLBACK_INPUT
), Animation
(CALLBACK_ANIMATION
和 CALLBACK_INSETS_ANIMATION
), Traversal
(CALLBACK_TRAVERSAL
) 的任务队列。CallbackQueue
中的实现是链表,执行完的任务就会被移除链表,他们的具体代码我就不贴了,感兴趣的自己去看一下。
接下来看看上面提到的任务处理的入口函数 doFrame
。
在开始任务前,会通过当前的时间与 Vsync
产生的时间作差然后和帧间隔相比,如果大于帧间隔就表示前面帧处理的时间过长,导致了掉帧:
Java
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
// ...
long intendedFrameTimeNanos = frameTimeNanos;
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.");
}
final long lastFrameOffset = jitterNanos % frameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (frameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}
//...
}
上面的代码块计算了掉帧,根据掉帧的 offset 重新计算了帧时间。
下面的代码添加了两个跳过 frame 绘制的逻辑,跳过本次绘制后会通过 scheduleVsyncLocked()
方法在再次请求下次的 Vsync
信号。
Java
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
// ...
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
traceMessage("Frame time goes backward");
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
traceMessage("Frame skipped due to FPSDivisor");
scheduleVsyncLocked();
return;
}
}
//...
}
如果当前的帧时间戳逼上次的帧时间戳还小的时候跳过本次绘制(通常是前面的帧绘制超时导致的);还有就是通过 mFPSDivisor
来限制刷新时间间隔,这样可以控制帧率,通常这个值是 1,也就是不限制。
以下就进入任务执行了:
Java
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
// ...
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
frameIntervalNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
//...
}
其中 mFrameInfo
中记录了很多有用的信息,包括 Vsync
的开始时间、帧间隔、绘制完成的deadline、每个阶段的耗时等信息,在应用性能分析时这些数据很有用。
任务按 CALLBACK_INPUT
, CALLBACK_ANIMATION
, CALLBACK_INSETS_ANIMATION
, CALLBACK_TRAVERSAL
依次执行,最后的实际任务执行都调用了 doCallbacks
方法:
Java
void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
// We use "now" to determine when callbacks become due because it's possible
// for earlier processing phases in a frame to post callbacks that should run
// in a following phase, such as an input event that causes an animation to start.
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
// Update the frame time if necessary when committing the frame.
// We only update the frame time if we are more than 2 frames late reaching
// the commit phase. This ensures that the frame time which is observed by the
// callbacks will always increase from one frame to the next and never repeat.
// We never want the next frame's starting frame time to end up being less than
// or equal to the previous frame's commit frame time. Keep in mind that the
// next frame has most likely already been scheduled by now so we play it
// safe by ensuring the commit time is always at least one frame behind.
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
if (jitterNanos >= 2 * frameIntervalNanos) {
final long lastFrameOffset = jitterNanos % frameIntervalNanos
+ frameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ " ms which is more than twice the frame interval of "
+ (frameIntervalNanos * 0.000001f) + " ms! "
+ "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ " ms in the past.");
mDebugPrintNextFrameTimeDelta = true;
}
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
首先通过需要执行的类型从 mCallbackQueues
中拿到想要的任务队列,然后根据时间把过期的 callback 过滤掉,如果是 CALLBACK_COMMIT
(Vsync
任务的最后一种类型) 还会记录一些超时的日志和更新一些状态相关的变量。然后依次执行任务,Callback 对应的类是 CallbackRecord
,执行时调用对应的 run
方法。最后回收这些 Callback。
Java
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
CallbackRecord
中的代码真的非常简单,就不多说了.
再看看 View 的请求绘制和动画
View 的请求绘制
众所周知如果需要更新 View 的 UI,需要调用 View#invalidate()
方法。最后几经跳转会执行下面的代码:
Java
// ...
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
// ...
他会调用 parent 的 invalidateChild
方法,我们继续跟进 ViewGroup
的 invalidateChild
方法,发现它也会调用它的 parent 的 invalidateChild
方法,也就是说它会一层一层往上调用,直到顶层的 parent,这里我直接说结论,顶层的 parent 就是 ViewRootImpl
。
在 ViewRootImpl#invalidateChild
方法中最后会兜兜转转调用到 scheduleTraversal
方法中。
Java
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
经过层层调用,最终终于看到了我们熟悉的 Choreographer
,他在主线程的 Looper 队列中加了一个屏障(提高任务的优先级), 然后将 mTraversalRunnable
添加到 CALLBACK_TRAVERSAL
队列中,等待下次 Vsync
信号来的时候执行。
Java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
TraversalRunnable
直接在 run
中调用 doTraversal
方法,这个方法就是触发整个 ViewTree measure、layout 和 draw 的方法。
在 Choreographer#postCallback
方法中最后会调用到 postCallbackDelayedInternal
方法中:
Java
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + 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);
}
}
}
首先将任务添加到对应 callbackType 的队列中,如果请求的时间小于等于当前时间,直接请求下次的 Vsync
信号;如果还未到执行的时间,那就通过 handler 发一个延迟任务,时间到了再请求 Vsync
信号.
View 动画
我们直接看 View#postOnAnimation
方法:
Java
public void postOnAnimation(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.mChoreographer.postCallback(
Choreographer.CALLBACK_ANIMATION, action, null);
} else {
// Postpone the runnable until we know
// on which thread it needs to run.
getRunQueue().post(action);
}
}
一点弯子都不绕,直接向 Choreographer
中直接添加 CALLBACK_ANIMATION
任务.