一、什么是屏幕刷新机制
屏幕的刷新包括三个步骤:
- CPU 计算屏幕数据
- GPU 进一步处理和缓存
- Display 将缓存中(buffer)的屏幕数据显示出来。
屏幕刷新机制包含以下几点要素,需要我们了解和掌握:
-
View 发起刷新的操作时,最终是走到了 ViewRootImpl 的 scheduleTraversals() 里去,然后这个方法会将遍历绘制 View 树的操作 performTraversals() 封装到 Runnable 里,传给 Choreographer,以当前的时间戳放进一个 mCallbackQueue 队列里,然后调用了 native 层的方法向底层注册监听下一个屏幕刷新信号事件。
-
当下一个屏幕刷新信号发出的时候,如果对这个事件进行监听,那么底层会回调 onVsync() 方法来通知。当 onVsync() 被回调时,会发一个 Message 到主线程,将后续的工作切到主线程来执行。切到主线程的工作就是去 mCallbackQueue 队列里根据时间戳将之前放进去的 Runnable 取出来执行,而这些 Runnable 就是遍历绘制 View 树的操作 performTraversals()。遍历操作完成后,就会去绘制那些需要刷新的 View。
-
当我们调用了 invalidate(),requestLayout(),等之类刷新界面的操作时,并不是马上就会执行这些刷新的操作,而是通过 ViewRootImpl 的 scheduleTraversals() 先向底层注册监听下一个屏幕刷新信号事件,然后等下一个屏幕刷新信号来的时候,才会去通过 performTraversals() 遍历绘制 View 树来执行这些刷新操作。
-
导致界面刷新丢帧的原因有两类:一是遍历绘制 View 树计算屏幕数据的时间超过了 16.6ms;二是,主线程一直在处理其他耗时的消息,导致遍历绘制 View 树的工作迟迟不能开始,从而超过了 16.6 ms 底层切换下一帧画面的时机。第一个原因是因为我们写的布局有问题,需要进行优化了。而第二个原因则是我们常说的避免在主线程中做耗时的任务。针对第二个原因,系统已经引入了同步屏障消息的机制,尽可能的保证遍历绘制 View 树的工作能够及时进行,但仍没办法完全避免,所以我们还是得尽可能避免主线程耗时工作。
二、Choreographer机制
Choreographer机制,用于同Vsync机制配合,统一动画、输入和绘制的时机。
本节讲解Choreographer机制主要是从绘制方面做阐述,界面的绘制要从ViewRootImpl的requestLayout开始说起,其源码如下:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread(); // 检查是否在UI线程
mLayoutRequested = true; // 是否进行measure和layout布局
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 拦截同步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 执行绘制操作
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这里需要注意的地方有两点:
- postSyncBarrier()方法:Handler 的同步屏障,作用是可以拦截 Looper 对同步消息的获取和分发,加入同步屏障之后Looper 只会获取和处理异步消息,如果没有异步消息那么就会进入阻塞状态。通过同步屏障,就为UI绘制渲染处理操作的优先处理提供了基础。
- Choreographer:编舞者,统一动画、输入和绘制时机。
1. Choreographer 启动逻辑
Choreographer的创建是在ViewRootImpl的构造函数中。
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
...
mChoreographer = Choreographer.getInstance();
...
}
下面是Choreographer的getInstance执行的代码:
/**
* Gets the choreographer for the calling thread. Must be called from
* a thread that already has a {@link android.os.Looper} associated with it.
*
* @return The choreographer for this thread.
* @throws IllegalStateException if the thread does not have a looper.
*/
public static Choreographer getInstance() {
return sThreadInstance.get();
}
// Thread local storage for the 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!");
}
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
private static volatile Choreographer mMainInstance;
可以看到每一个Looper线程都有自己的Choreographer,其他线程发送的回调只能运行在对应Choreographer所属的Looper线程上。
这里我们再看一下Choreographer的构造函数:
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
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类中有一个Looper和一个FrameHandler变量。变量USE_VSYNC用于表示系统是否是用了Vsync同步机制,该值是通过读取系统属性debug.choreographer.vsync来获取的。如果系统使用了Vsync同步机制,则创建一个FrameDisplayEventReceiver对象用于请求并接收Vsync事件,最后Choreographer创建了一个大小为3的CallbackQueue队列数组,用于保存不同类型的Callback。
不同类型的Callback包括如下4种:
CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT。
CallbackQueue是一个容量为4的数组,每一个元素作为头指针,引出对应类型的链表,4种事件就是通过这4个链表来维护的。而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(); // 请求VSYNC信号
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}
2. Choreographer执行流程
执行流程就要需要从下面的代码开始说起:
// 执行绘制操作 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
最终会调到 postCallbackDelayedInternal 方法:
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
synchronized (mLock) {
// 当前时间
final long now = SystemClock.uptimeMillis();
// 回调执行时间,为当前时间加上延迟的时间
final long dueTime = now + delayMillis;
// obtainCallbackLocked会将传入的3个参数转换为CallbackRecord,然后CallbackQueue根据回调类型将CallbackRecord添加到链表上。
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime >= now) {
// 如果delayMillis=0的话,dueTime=now,则会马上执行
scheduleFrameLocked(now);
} else {
// 如果dueTime > now,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理,其最终处理也是执行scheduleFrameLocked(long now)方法
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
mCallbackQueues先把对应的callback添加到链表上来,然后判断是否有延迟,如果没有则会马上执行scheduleFrameLocked,如果有,则发送一个what为MSG_DO_SCHEDULE_CALLBACK类型的定时消息,等时间到了再处理,其最终处理也是执行scheduleFrameLocked(long now)方法。
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
// 如果使用了VSYNC,由系统值确定
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
if (isRunningOnLooperThreadLocked()) {
// 请求VSYNC信号,最终会调到Native层,Native处理完成后触发FrameDisplayEventReceiver的onVsync回调,回调中最后也会调用doFrame(long frameTimeNanos, int frame)方法
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 {
// 没有使用VSYNC
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(long frameTimeNanos, int frame)方法
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
判断USE_VSYNC,如果使用了VSYNC,走scheduleVsyncLocked,即请求VSYNC信号,最终调用doFrame;如果没使用VSYNC,则通过异步Message执行doFrame。
下面我们看一下doFrame的代码:
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // mFrameScheduled=false,则直接返回。
}
long intendedFrameTimeNanos = frameTimeNanos; //原本计划的绘帧时间点
startNanos = System.nanoTime();//保存起始时间
//由于Vsync事件处理采用的是异步方式,因此这里计算消息发送与函数调用开始之间所花费的时间
final long jitterNanos = startNanos - frameTimeNanos;
//如果线程处理该消息的时间超过了屏幕刷新周期
if (jitterNanos >= mFrameIntervalNanos) {
//计算函数调用期间所错过的帧数
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
//当掉帧个数超过30,则输出相应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;
frameTimeNanos = startNanos - lastFrameOffset; //对齐帧的时间间隔
}
//如果frameTimeNanos小于一个屏幕刷新周期,则重新请求VSync信号
if (frameTimeNanos < mLastFrameTimeNanos) {
scheduleVsyncLocked();
return;
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
//分别回调CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL事件
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);
}
}
doCallBacks里的run方法执行了,也就是真正执行了View的绘制流程了。
3.Choreographer总结
1). Choreographer支持4种类型事件:输入、绘制、动画、提交,并通过postCallback在对应需要同步vsync进行刷新处进行注册,等待回调。
2). Choreographer监听底层Vsync信号,一旦接收到回调信号,则通过doFrame统一对java层4种类型事件进行回调。
三、屏幕绘制过程的流程图