前言
Android系统从4.1(API 16)开始加入Choreographer机制,用于同Vsync机制统一处理输入(Input)、动画(Animation)、绘制(Draw)三个UI操作。其实UI显示的时候每一帧要完成的事情只有这三种。Choreographer接收显示系统的时间脉冲(垂直同步信号-VSync信号),在下一个frame渲染时控制执行这些操作。
如何理解Choreographer?
1. Choreographer的源码位于android.view这个pakage中,是view层框架的一部分。 2. Choreographer中文翻译过来是"舞蹈指挥",字面上的意思就是优雅地指挥以上三个UI操作一起跳一支舞。这个词可以概括这个类的工作,如果android系统是一场芭蕾舞,他就是Android UI显示这出精彩舞剧的编舞,指挥台上的演员们相互合作,精彩演出。
3. 开发者可以使用Choreographer#postFrameCallback设置自己的callback与Choreographer交互,你设置的callCack会在下一个frame被渲染时触发。 4. Callback有如下5种类型:.
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;
收到VSync信号后,顺序执行3个操作,然后等待下一个信号,再次顺序执行3个操作。
一、从绘制流程开始
ViewRootImpl的requestLayout开启绘制流程:
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
//是否measure和layout布局
mLayoutRequested = true;
scheduleTraversals();
}
}
...
@UnsupportedAppUsage
void scheduleTraversals() {
...
//同一帧内不会多次调用遍历
if(!mTraversalScheduled) {
mTraversalScheduled = true;
//拦截同步message
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
...
}
Choreographer提供了两种添加回调的方式:postCallback、postFrameCallback,
/**
* Coordinates the timing of animations, input and drawing.
* <p>
* The choreographer receives timing pulses (such as vertical synchronization)
* from the display subsystem then schedules work to occur as part of rendering
* the next display frame.
* </p><p>
* Applications typically interact with the choreographer indirectly using
* higher level abstractions in the animation framework or the view hierarchy.
* Here are some examples of things you can do using the higher-level APIs.
* </p>
* <ul>
* <li>To post an animation to be processed on a regular time basis synchronized with
* display frame rendering, use {@link android.animation.ValueAnimator#start}.</li>
* <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
* frame, use {@link View#postOnAnimation}.</li>
* <li>To post a {@link Runnable} to be invoked once at the beginning of the next display
* frame after a delay, use {@link View#postOnAnimationDelayed}.</li>
* <li>To post a call to {@link View#invalidate()} to occur once at the beginning of the
* next display frame, use {@link View#postInvalidateOnAnimation()} or
* {@link View#postInvalidateOnAnimation(int, int, int, int)}.</li>
* <li>To ensure that the contents of a {@link View} scroll smoothly and are drawn in
* sync with display frame rendering, do nothing. This already happens automatically.
* {@link View#onDraw} will be called at the appropriate time.</li>
* </ul>
* <p>
* However, there are a few cases where you might want to use the functions of the
* choreographer directly in your application. Here are some examples.
* </p>
* <ul>
* <li>If your application does its rendering in a different thread, possibly using GL,
* or does not use the animation framework or view hierarchy at all
* and you want to ensure that it is appropriately synchronized with the display, then use
* {@link Choreographer#postFrameCallback}.</li>
* <li>... and that's about it.</li>
* </ul>
* <p>
* Each {@link Looper} thread has its own choreographer. Other threads can
* post callbacks to run on the choreographer but they will run on the {@link Looper}
* to which the choreographer belongs.
* </p>
*/
public final class Choreographer {
...
/**
* Posts a callback to run on the next frame.
* <p>
* The callback runs once then is automatically removed.
* </p>
*
* @param callbackType The callback type.
* @param action The callback action to run during the next frame.
* @param token The callback token, or null if none.
*
* @see #removeCallbacks
* @hide
*/
@TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
...
/**
* Posts a frame callback to run on the next frame.
* <p>
* The callback runs once then is automatically removed.
* </p>
*
* @param callback The frame callback to run during the next frame.
*
* @see #postFrameCallbackDelayed
* @see #removeFrameCallback
*/
public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}
...
}
两者最终都会调用到postCallbackDelayedInternal
public final class Choreographer {
...
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
//当前时间
final long now = SystemClock.uptimeMillis();
//回调执行时间,为当前时间加上延迟的时间
final long dueTime = now + delayMillis;
//addCallbackLocked会将传入的3个参数转换成CallbackRecord,然后CallbackQueue根据回调类型将CallbackRecord添加到链表上。
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
//如果delayMillis=0的话,dueTime = now,则会马上执行
scheduleFrameLocked(now);
} else {
//dueTime > now,发送MSG_DO_SCHEDULE_CALLBACK消息,等时间到了再处理
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
...
}
CallbackQueue是一个容量为4的数组,每一个元素作为头指针,引出对应类型的链表,4种事件就是通过这4个链表来维护的。
mCallbackQueues先把对应的callback添加到链表上来,然后判断是否有延迟,如果没有则马上执行scheduleFrameLocked,如果有延迟,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理,其最终的处理也是scheduleFrameLocked方法。
public final class Choreographer {
...
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);
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}
...
void doScheduleCallback(int callbackType) {
synchronized (mLock) {
if (!mFrameScheduled) {
final long now = SystemClock.uptimeMillis();
if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
scheduleFrameLocked(now);
}
}
}
}
...
}
public final class Choreographer {
...
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
...
//使用了USE_VSYNC
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
//请求VSYNC信号,会调到native层,native层处理完成后触发FrameDisplayEventReceiver的onVsync方法,最终会调用doFrame方法
//此流程后续分析
scheduleVsyncLocked();
} else {
//在UI线程上直接发送一个what=MSG_DO_SCHEDULE_VSYNC的消息,最终也会调用到scheduleVsyncLocked方法去请求VSYNC信号
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);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
//直接发送一个what=MSG_DO_FRAME的消息,消息处理时调用doFrame方法。
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
...
void doScheduleVsync() {
synchronized (mLock) {
if (mFrameScheduled) {
scheduleVsyncLocked();
}
}
}
...
@UnsupportedAppUsage
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
mIsVsyncScheduled = true;
}
...
}
/**
* Provides a low-level mechanism for an application to receive display events
* such as vertical sync.
*
* The display event receive is NOT thread safe. Moreover, its methods must only
* be called on the Looper thread to which it is attached.
*
* @hide
*/
public abstract class DisplayEventReceiver {
...
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
@UnsupportedAppUsage
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 {
nativeScheduleVsync(mReceiverPtr);
}
}
...
}
请求VSYNC信号的流程在下一篇分析,本节继续分析接收VSYNC信号及后续的处理流程。
回调在FrameDisplayEventReceiver的onVsync方法:
public final class Choreographer {
...
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
//mTimestampNanos是信号到来的时间参数
private long mTimestampNanos;
private int mFrame;
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
// 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) {
// 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;
//该消息的callback为当前对象FrameDisplayEventReceiver,mHandler为FrameHandler
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);
}
}
...
}
onVsync方法是通过FrameHandler向主线程发送了一个自带callback的消息,当主线程执行到该消息时,则调用FrameDisplayEventReceiver.run(),紧接着是调用doFrame。消息分发不是本节重点,可以参见Handler源码了解详细过程。
public final class Choreographer {
...
@UnsupportedAppUsage
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
...
synchronized (mLock) {
mIsVsyncScheduled = false;
//判断是否有callback需要执行,mFrameScheduled会在postCallBack的时候置为true,一次frame执行时置为false
if (!mFrameScheduled) {
return; // no work to do
}
//打印跳frame时间
if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
mDebugPrintNextFrameTimeDelta = false;
Log.d(TAG, "Frame time delta: "
+ ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
}
//设置当前frame的Vsync信号到来时间(原本计划的绘帧时间点)
long intendedFrameTimeNanos = frameTimeNanos;
//实际开始执行当前frame的时间
startNanos = System.nanoTime();
//时间差(由于Vsync事件处理采用的是异步方式,因此这里计算消息发送与函数调用开始之间所花费的时间)
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
//时间差大于一个时钟周期,认为跳frame
//计算函数调用期间所错过的帧数
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
//跳frame数大于默认值,打印警告信息,默认值为30
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
...
//计算实际开始当前frame与时钟信号的偏差值
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
if (DEBUG_JANK) {
//打印偏差及跳帧信息
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
//修正偏差值,忽略偏差,为了后续更好地同步工作
frameTimeNanos = startNanos - lastFrameOffset;
}
//若时间回溯,则不进行任何工作,等待下一个时钟信号的到来
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.");
}
//请求下一次时钟信号
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
}
}
//记录当前frame信息
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
//记录上一次frame开始时间,修正后的
mLastFrameTimeNanos = frameTimeNanos;
}
try {
//执行相关callBack
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, 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);
}
...
if (DEBUG_FRAMES) {
final long endNanos = System.nanoTime();
Log.d(TAG, "Frame " + frame + ": Finished, took "
+ (endNanos - startNanos) * 0.000001f + " ms, latency "
+ (startNanos - frameTimeNanos) * 0.000001f + " ms.");
}
}
...
}
doFrame的大致流程如下:
总结起来其实主要是两个操作:
1.设置当前frame的启动时间。
判断是否跳帧,若跳帧修正当前frame的启动时间到最近的VSync信号时间。如果没跳帧,当前frame启动时间直接设置为当前VSync信号时间。修正完时间后,无论当前frame是否跳帧,使得当前frame的启动时间与VSync信号还是在一个节奏上的,可能延后了一到几个周期,但是节奏点还是吻合的。
如下图所示是时间修正的一个例子,
由于第二个frame执行超时,第三个frame实际启动时间比第三个VSync信号到来时间要晚,因为这时候延时比较小,没有超过一个时钟周期,系统还是将frameTimeNanos3传给回调,回调拿到的时间和VSync信号同步。再来看看下图:
由于第二个frame执行时间超过2个时钟周期,导致第三个frame延后执行时间大于一个时钟周期,系统认为这时候影响较大,判定为跳帧了,将第三个frame的时间修正为frameTimeNanos4,比VSync真正到来的时间晚了一个时钟周期。时间修正,既保证了doFrame操作和VSync保持同步节奏,又保证实际启动时间与记录的时间点相差不会太大,便于同步及分析。
接下来继续分析doCallbacks的执行过程
public final class Choreographer {
...
void doCallbacks(int callbackType, long frameTimeNanos) {
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 * 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 {
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);
}
}
...
private final class CallbackQueue {
private CallbackRecord mHead;
public boolean hasDueCallbacksLocked(long now) {
return mHead != null && mHead.dueTime <= now;
}
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的类型有以下4种,
CALLBACK_INPUT:输入
CALLBACK_ANIMATION:动画
CALLBACK_TRAVERSAL:遍历,执行measure、layout、draw
CALLBACK_COMMIT:遍历完成的提交操作,用来修正动画启动时间
然后看上面的源码,分析一下每个callback的执行过程:
1. callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS);得到执行时间在当前时间之前的所有CallBack,保存在单链表中。每种类型的callback按执行时间先后顺序排序分别存在一个单链表里面。为了保证当前callback执行时新post进来的callback在下一个frame时才被执行,这个地方extractDueCallbacksLocked会将需要执行的callback和以后执行的callback断开变成两个链表,新post进来的callback会被放到后面一个链表中。当前frame只会执行前一个链表中的callback,保证了在执行callback时,如果callback中Post相同类型的callback,这些新加的callback将在下一个frame启动后才会被执行。
2. 如果类型是CALLBACK_COMMIT,并且当前frame渲染时间超过了两个时钟周期,则将当前提交时间修正为上一个垂直同步信号时间。为了保证下一个frame的提交时间和当前frame时间相差为一且不重复。
3. 调用c.run(frameTimeNanos),执行回调。例如ViewRootImpl中的回调TraversalRunnable。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
...
}
二、Choreographer执行流程
三、整个绘制过程总的流程
来源:CSDN
作者:小人物梦想大世界
链接:https://blog.csdn.net/qq2234385378/article/details/103242281