参考:
//www.greatytc.com/p/996bca12eb1d
//www.greatytc.com/p/dd32ec35db1d
//www.greatytc.com/p/c2d93861095a
//www.greatytc.com/p/6f2043570de4
一.概念
1.Choreographer接收显示系统的VSync信号,在下一个frame渲染时控制输入(Input)、动画(Animation)、绘制(Draw)三个UI操作。其实UI显示的时候每一帧要完成的事情只有这三种。
2.开发者可以使用Choreographer#postFrameCallback设置自己的callback与Choreographer交互,你设置的callCack会在下一个frame被渲染时触发。
3.Callback目前有4种类型,Input、Animation、Draw、Commit。
4.每个进程的UI线程都有自己的choreographer,应用层的一个Activity对应一个根View(也就是一个DecorView)、一个WindowState、一个ViewRootImpl
6.如果掉帧数>=30的话会打印下面的log:
I/Choreographer: Skipped 55 frames! The application may be doing too much work on its main thread.
同时也可以修改默认30帧,方法如下:
getprop debug.choreographer.skipwarning //读取
setprop debug.choreographer.skipwarning 35 //修改
setprop ctl.restart surfaceflinger; setprop ctl.restart zygote //重启
二.代码分析
1.请求vsync
app必须首先请求vsync,然后接收vsync后才进行绘制,而且每次都需要请求vsync,不是一劳永逸。
1.1 代码UML图
ViewRootImpl#scheduleTraversals遍历视图树的每一个节点,完成一个树形视图的绘制过程。每当ViewRootImpl安排一次遍历scheduleTraversals,将mTraversalScheduled置true,表示已经schedule过了traversal,防止多次安排,然后发送同步栅栏。当执行遍历doTraversal或主动取消遍历unscheduleTraversals时,会关掉标志位mTraversalScheduled,取消同步栅栏。委托Choreographer安排遍历,请求信号。
mTraversalScheduled表示是否已经发起重绘,每次scheduleTraversals()方法调用之后,就会将它置为true,然后在下一帧的时候调用doTraversal()又先将它置为false,然后调用mChoreographer.postCallback()添加一个Runnable。
frameworks/base/core/java/android/view/ViewRootImpl.java
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
同样也存在其他的input和animation的postCallback:
mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, ...
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, ...
下面看下这3种方式在ViewRootImpl中的调用:
//方式1:CALLBACK_TRAVERSAL
void scheduleTraversals() {
...
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
//方式2:CALLBACK_INPUT
final class ConsumeBatchedInputRunnable implements Runnable {
@Override
public void run() {
doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
}
}
final ConsumeBatchedInputRunnable mConsumedBatchedInputRunnable =
new ConsumeBatchedInputRunnable();
void scheduleConsumeBatchedInput() {
mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
mConsumedBatchedInputRunnable, null);
//方式3:CALLBACK_ANIMATION
final class InvalidateOnAnimationRunnable implements Runnable {
...
public void run() {
...
for (int i = 0; i < viewRectCount; i++) {
final View.AttachInfo.InvalidateInfo info = mTempViewRects[i];
info.target.invalidate(info.left, info.top, info.right, info.bottom);
info.recycle();
}
private void postIfNeededLocked() {
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
1.2 流程图
ViewRootImpl负责接收input、animation、traversal,交由“编舞者”Choreographer处理。下面分析下过程:
1)CallbackQueue数组,数组元素是CallbackQueue对象,内部包含指向CallbackRecord链表的头指针。四种callbackType类型:CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT。每一种callbackType类型代表数组的一个索引;CallbackQueue#addCallbackLocked方法,创建一个CallbackRecord对象,封装dueTime执行时间(当前时间+延迟),任务action和token。将CallbackRecord插入链表,按照dueTime时间排序决定插入位置,dueTime小的位于链表的前面。
2)当dueTime还没有到的话(做了延迟)就会向UI线程发消息,做到异步,FrameHandler主要就是处理这些动作,详细的代码见下。如果dueTime到了或者是UI线程已经处理完了这些消息后会调用scheduleFrameLocked。
可以看到FrameHandler主要处理MSG_DO_FRAME、MSG_DO_SCHEDULE_VSYNC、MSG_DO_SCHEDULE_CALLBACK动作;
frameworks/base/core/java/android/view/Choreographer.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(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}
3)根据mFrameScheduled(该帧是否已经处理过了)的值决定是否做最终的doFrame;mFrameScheduled控制scheduleFrameLocked和doFrame,两者互相影响。在第一次的scheduleFrameLocked中mFrameScheduled会被置为true,表示有帧需要处理了,在doFrame中会判断这个值,如果为false的话就表示没有帧处理,即no work to do,因为为true,所以在处理完成后会将值置为false,进行下一次循环。
Choreographer中有dump方法会打印该值,一会儿研究一下~
4)然后判断是否有vsync,没有的话在发送MSG_DO_FRAME给FrameHandler去处理doFrame;
5)然后判断vsync在UI thread还是在其它,如果是非UI的话发送MSG_DO_SCHEDULE_VSYNC给FrameHandler去处理doScheduleVsync,最终都会调用scheduleVsyncLocked。
6)最后调用native的vsync。
2.接收vsync
2.2代码UML图
java层接收到的vsync从底层上报的,具体流程如下:
01-02 15:12:37.132 1400 1400 W System.err: java.lang.RuntimeException
01-02 15:12:37.133 1400 1400 W System.err: at android.view.Choreographer$FrameDisplayEventReceiver.onVsync(Choreographer.java:850)
01-02 15:12:37.133 1400 1400 W System.err: at android.view.DisplayEventReceiver.dispatchVsync(DisplayEventReceiver.java:171)
01-02 15:12:37.133 1400 1400 W System.err: at android.os.MessageQueue.nativePollOnce(Native Method)
01-02 15:12:37.133 1400 1400 W System.err: at android.os.MessageQueue.next(MessageQueue.java:325)
01-02 15:12:37.133 1400 1400 W System.err: at android.os.Looper.loop(Looper.java:142)
01-02 15:12:37.133 1400 1400 W System.err: at android.app.ActivityThread.main(ActivityThread.java:6722)
01-02 15:12:37.133 1400 1400 W System.err: at java.lang.reflect.Method.invoke(Native Method)
01-02 15:12:37.133 1400 1400 W System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:449)
01-02 15:12:37.133 1400 1400 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
2.2流程图
FrameDisplayEventReceiver接收vsync(onvsync)后会做doFrame动作。
3.总结
ViewRootimpl在处理完上一帧数据后会重新遍历下view树会postCallback请求下一帧的vsync,driver产生vsync后会回调onVsync函数接收,传入frameNanos和frame,接收后调用doFrame动作。
ViewRootImpl postCallback -> TraversalRunnable run, 那么我们可以自定义方法回调doFrame,详细原因可见下面doCallback部分的分析:
public class FPSFrameCallback implements Choreographer.FrameCallback{
@Override
public void doFrame(long frameTimeNanos){
//do something
}
}
Choreographer.getInstance().postFrameCallback(new FPSFrameCallback());
4.doFrame
4.1流程图
当出现掉帧时,会对 frameTimeNanos 进行修正,修正到最后一次 VSync 的时间;当 frameTimeNanos < mLastFrameTimeNanos 时,请求 VSync 然后 return,相当于忽略本次信号,等待下一个信号。但这个条件不太可能通过,有可能的情况比如 System.nanoTime() 发生倒退(比如修改手机时间),反正这个条件通过就代表出现异常情况。
4.2代码流程
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) { //判断是否有callback需要执行,mFrameScheduled会在postCallBack的时候置为true(象征vsync信号到了),一次frame执行时置为false(象征该帧已渲染完毕)
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;
startNanos = System.nanoTime();//实际开始执行当前frame的时间
//时间差
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;
}
//若时间回溯,则不进行任何工作,等待下一个时钟信号的到来
//这里为什么会发生时间回溯我没搞明白,大概是未知时钟错误引起?注释里说的maybe 好像不太对
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;
}
//记录当前frame信息,intendedFrameTimeNanos(底层上传的vsync时间戳)
//frameTimeNanos(真实的vsync时间戳,可能往后推几个vsync)
mFrameInfo.setVsync(intendedFrameTimeNanos,frameTimeNanos);
mFrameScheduled = false;
//记录上一次frame开始时间,修正后的
mLastFrameTimeNanos = frameTimeNanos;
}
try {
//执行相关callBack
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
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.");
}
}
4.3 doCallbacks
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
...
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
...
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);
}
private static final class CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token;
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}
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时间相差为一且不重复。
这个地方注释挺难看懂,实际上这个地方CALLBACK_COMMIT是为了解决ValueAnimator的一个问题而引入的,主要是解决因为遍历时间过长导致动画时间启动过长,时间缩短,导致跳帧,这里修正动画第一个frame开始时间延后来改善,这时候才表示动画真正启动。为什么不直接设置当前时间而是回溯一个时钟周期之前的时间呢?看注释,这里如果设置为当前frame时间,因为动画的第一个frame其实已经绘制完成,第二个frame这时候已经开始了,设置为当前时间会导致这两个frame时间一样,导致冲突。详细情况请看官方针对这个问题的修改(https://link.jianshu.com/?t=https://android.googlesource.com/platform/frameworks/base/+/c42b28d%5E!/);
3)最重要的是在CallbackRecord的run方法中根据token决定是走doFrame还是走runnable的run,看下面的postFrameCallback方法可以知道答案,对应前面自定的Callback时回调它的doFrame函数。
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,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
4.4 performTraversals
Choreographer doCallbacks -> TraversalRunnable run -> ViewRootImpl doTraversal -> ViewRootImpl performTraversal -> performDraw