在Android4.1之后增加了Choreographer机制,用于同Vsync机制配合,控制同步处理输入(Input)、动画(Animation)、绘制(Draw)三个UI操作。其实UI显示的时候每一帧要完成的事情只有这三种。如下图是官网的相关说明:
Choreographer接收显示系统的时间脉冲(垂直同步信号-VSync信号),在下一个frame渲染时控制执行这些操作。
Choreographer中文翻译过来是"舞蹈指挥",字面上的意思就是优雅地指挥以上三个UI操作一起跳一支舞。这个词可以概括这个类的工作,如果android系统是一场芭蕾舞,他就是Android UI显示这出精彩舞剧的编舞,指挥台上的演员们相互合作,精彩演出。
通过Choreographer#postFrameCallback设置callback,在下一个frame被渲染时,会执行这个callback,是在Choreographer 中的Looper 执行的。例如:View的绘制流程,就是通过postFrameCallback设置回调,然后被执行的。
Callback有4种类型,Input、Animation、Draw,还有一种是用来解决动画启动问题的。这四种操作都是这么触发的。
收到VSync信号后,顺序执行3个操作,然后等待下一个信号,再次顺序执行3个操作。假设在第二个信号到来之前,所有的操作都执行完成了,即Draw操作完成了,界面显示第一帧的内容。那么第二个信号来到时,开始更新界面。每帧的时间不应超过 16ms,以达到每秒 60 帧的呈现速度(为什么是 60fps?)视觉上就不会感觉卡顿。
如果您的应用存在界面呈现缓慢的问题,系统会不得不跳过一些帧,这会导致用户感觉您的应用不流畅。我们将这种情况称为卡顿。
关于 帧 的概念,就像是动画的每一格图像,每一格就像是一张图片,快速连续的播放,因为视觉的停留,看上去就是动态的。 而在Android系统中,这里的帧 是 指的是两个VSync信号之间的这段时间,而不是具体待显示的内容,跳帧的意思就是 例如 UI 操作A需要在16ms 内完成,但是它运行了35ms,那么下一个UI操作B,只能在35ms 后执行,本来UI操作B 要在16ms 开始执行的,我们认为它跳了一帧。(注意 UI操作B 执行的时候,Choreographer 会对齐VSync时钟,也就是说认为他是在32ms 开始执行的)
第二个信号到来时,Draw操作没有按时完成,导致第三个时钟周期内显示的还是第一帧的内容。
大家应该都熟悉界面的绘制流程了,下面看下ViewRootImpl的requestLayout
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//检查是否在主线程
mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局。
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {//同一帧内不会多次调用遍历
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//拦截同步Message
//Choreographer回调,执行绘制操作
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
public ViewRootImpl(Context context, Display display) {
...
//获取Choreographer实例
mChoreographer = Choreographer.getInstance();
...
}
public static Choreographer getInstance() {
return sThreadInstance.get();
}
//一个线程对应一个Choreographer
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
return new Choreographer(looper);
}
};
private Choreographer(Looper looper, int vsyncSource) {
//一个线程对应一个Looper,一个Choreographer
mLooper = looper;
//初始化FrameHandler。接收处理消息。满足条件的VSync信号的具体处理
mHandler = new FrameHandler(looper);
//初始化FrameDisplayEventReceiver。FrameDisplayEventReceiver用来接收垂直同步脉冲,就是VSync信号,VSync信号是一个时间脉冲,一般为60HZ。
//变量USE_VSYNC 表示是否用了Vsync同步机制
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
//每一帧的时间
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//回调队列,例如 postFrameCallback 传入的callback,在处理下一阵的时候,回调它
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));
}
CallbackQueue 队列的四种类型
public static final int CALLBACK_INPUT = 0; //输入
public static final int CALLBACK_ANIMATION = 1; //动画
public static final int CALLBACK_TRAVERSAL = 2; //视图绘制
public static final int CALLBACK_COMMIT = 3; //提交 ( 这一类型是在API level=23的时候添加的)
CallbackQueue是一个容量为4的数组,每一个元素作为头指针,引出对应类型的链表,4种事件就是通过这4个链表来维护的。其中每种类型都是使用单向链表来组织,和Message 中的消息组织方式、创建、回收都是一样的
先来大概看一下,整体流程,有个整体轮廓,具体分析每一个函数时,不会迷路
图片都是从网上找的,最后会给出原文链接
先来看看FrameHandler,因为下面很多发送消息的,都会到这里处理
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:
//处理 Callback
doScheduleCallback(msg.arg1);
break;
}
}
}
postCallback 和 postCallbackDelayed 最终都会调用到下面的postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
//获取当前时间
final long now = SystemClock.uptimeMillis();
//计算出截止时间
final long dueTime = now + delayMillis;
//action 就是回调接口,按照截止时间加入指定类型的队列中,队列截止时间顺序是由小到大
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
//当前时间已经超过截止时间了,直接执行
scheduleFrameLocked(now);
} else {
//当前时间未超过截止时间,发送msg,最终在FrameHandler 进行处理
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
加锁执行Frame,这个锁是在调用scheduleFrameLocked的时候,外部添加的synchronized
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_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()) {
scheduleVsyncLocked();
} else {
//通过handler 发送MSG_DO_SCHEDULE_VSYNC消息,最终也是调用scheduleVsyncLocked来处理的
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);
//通过handler 发送MSG_DO_FRAME消息,最终调用doFrame来处理
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
//请求垂直同步信号
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
系统是通过调用dispatchVsync,发送VSync信号的。接着调用 onVsync来处理。Choreographer里的有个类FrameDisplayEventReceiver,它继承了DisplayEventReceiver,并覆写了onVsync
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
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, int builtInDisplayId, int frame) {
// Ignore vsync from secondary display.
// This can be problematic because the call to scheduleVsync() is a one-shot.
// We need to ensure that we will still receive the vsync from the primary
// display which is the one we really care about. Ideally we should schedule
// vsync for a particular display.
// At this time Surface Flinger won't send us vsyncs for secondary displays
// but that could change in the future so let's log a message to help us remember
// that we need to fix this.
if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
Log.d(TAG, "Received vsync from secondary display, but we don't support "
+ "this case yet. Choreographer needs a way to explicitly request "
+ "vsync for a specific display to ensure it doesn't lose track "
+ "of its scheduled vsync.");
scheduleVsync();
return;
}
// 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 传入this,就是下面的run 函数
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);
}
}
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
//开始处理帧任务前(在scheduleFrameLocked中),设为true,doFrame 处理完后,设为false,表示一帧处理完后,才会接受下一帧的处理
if (!mFrameScheduled) {
return; // no work to do
}
//发送帧的时间
long intendedFrameTimeNanos = frameTimeNanos;
//获取当前时间
startNanos = System.nanoTime();
//计算出时间差,例如帧很早就发送了,但是过一会才得到处理
final long jitterNanos = startNanos - frameTimeNanos;
//时间差 大于帧的周期
if (jitterNanos >= mFrameIntervalNanos) {
//计算跳过了几帧
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
//下面这段log ,应该很熟悉了
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 % 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.");
}
//请求下一次VSync信号
scheduleVsyncLocked();
return;
}
if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
scheduleVsyncLocked();
return;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
//false 表示当前没有正在处理的帧
mFrameScheduled = false;
//记录上一次frame开始时间,修正后的
mLastFrameTimeNanos = frameTimeNanos;
}
try {
//在doCallbacks 函数中,执行postCallbackDelayedInternal传入的回调接口
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);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
关于上面帧对齐,下面来看两种情况:
当前frame启动时间直接设置为当前VSync信号时间。
虽然Frame 1执行时间超过一个VSync周期,但是因为没有跳帧(没有大于两个周期),frame 2的执行时间还是设置为frameTimeNanos3
修正当前frame的启动时间到最近的VSync信号时间。
frame 1 执行时间超过2个时钟周期,frame 2 延后执行时间大于一个时钟周期,系统认为这时候影响较大,判定为跳帧了,将frame 2的时间修正为frameTimeNanos4,比VSync真正到来的时间晚了一个时钟周期。
无论当前frame是否跳帧,修正完时间后,frame的处理时间与VSync信号的发送时间还是在一个节奏上的,可能延后了N个周期
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();
//extractDueCallbacksLocked 函数就是按照 当前的时间,把队列划分为两部分。
//因为队列是按照到期时间由小到大 排列的,以当前时间为界,分为两部分。返回小的部分队列的头,大的部分队列的头赋值给mHead
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
//提交帧时,如有必要,更新帧时间。
//仅在到达提交阶段晚于2帧时才更新帧时间。
//这确保了回调所观察到的帧时间将始终从一帧增加到下一帧,并且永远不会重复。
//我们永远都不希望下一帧的开始帧时间小于或等于前一帧的提交帧时间。
//请记住,下一帧很可能已经安排好了,因此我们可以确保提交时间始终至少落后一帧,以确保安全
//CALLBACK_COMMIT 是为了解决,动画绘制时间过长导致的动画跳帧的问题
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
//时间差大于2倍的VSync周期,才去修正帧时间。
//是因为下面会把帧时间,往前移动一个完整周期,避免出现两个帧的
if (jitterNanos >= 2 * mFrameIntervalNanos) {
//周期偏差时间 + 一个周期的时间
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
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) {
//开始回调接口
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);
}
}
上图表达的不是很清楚,不用理会”修正commit time“虚线,frameTimeNanos2 修正到第三个VSync(修正后的commit time 实线指向的时间)
在 frame 1 开始执行时,开始渲染动画的第一个画面,frame 1 执行时间超过了两个时钟周期,Draw操作执行结束后,这时候完成了动画第一帧的渲染,动画实际上还没开始,但是时间已经过了两个时钟周期,此时还没有开始动画的执行,动画是使用插值器,根据时间的流逝来计算比例。因此开始时间就显得很关键,如果在第一帧渲染就使用了过多的时间,那么就需要调整动画的开始时间,这样动画才能在第一帧开始播放
也就是说动画的开始时间需要往后移,移动多少呢?帧处理结束的时间 - (周期的偏移时间 + 一个周期时间)
为什么要移动这么多时间呢? 为了避免两个frame 的帧时间相同,还记得前面doFrame函数吗,它会把frame操作的到来时间 对齐到 VSync的频率上,而且是向前对齐。也就是说如果frame 1 只是移动到帧处理结束的时间 - (周期的偏移时间),那么就会和frame 2 的帧时间相同(以上图来说,就是都是frameTimeNanos3)
看了这么多,感觉终于理解了,但是在Android 8.0.0 源码中ValueAnimator 动画的开始时间调整,已经不使用这种方式了。也就是说CALLBACK_COMMIT
没有被使用了, 那又是如何解决这个问题的呢?我会在下片文章分析这个问题
队列中保存的就是该类对象,执行到该类的run函数
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();
}
}
}
回调requestLayout 中传入的接口
void scheduleTraversals(){
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable,null);
}
final class TraversalRunnable implements Runnable{
@Override
public void run(){
//开始绘制流程,会执行layout,measure,draw
doTraversal();
}
}
参考:
Android Choreographer 源码分析
提取该文章的部分一张图片
Android 基于 Choreographer 的渲染机制详解
提取该文章的部分一张图片,它的部分代码理解有误
Android图形系统(十一)-Choreographer