之前在 Android性能优化中的刷新机制 中大概了解过 Android 的刷新机制,今天再带着问题了解一下 Android UI 的刷新机制。
首先:应用从系统服务申请 buffer ,系统服务返回给应用 buffer,应用拿到 buffer 之后进行绘制,绘制完成后交给系统服务。
系统服务会将 buffer 写到缓冲区里面,屏幕会有一定的帧率刷新,每次刷新会从缓冲区中取出 buffer 然后显示出来。
如果没有新的数据可以取就一直从老的数据,这样看起来屏幕就一直没有变,这就是基本的显示原理,如下图
系统服务并没有用一个缓存,因为如果屏幕正在读缓存,这时候正好又写缓存,可能导致屏幕显示的东西不正常,所以用的是两个缓存,一个读一个写,如果要显示下一帧,将两个缓存交换就行了。
屏幕是根据 vsync 信号周期性的刷新的,vsync 信号是一个固定频率的脉冲信号,屏幕每次收到 vsync 信号就会去缓冲区取出一帧信号进行显示。绘制是由客户端随时发起的。
上面这个图,第一个vsync 屏幕显示的是第 0 帧图像,第一个周期显示的是第 1 帧图像,因为第一帧在 Vsync 来的时候已经准备好了,第三个信号周期还是显示的是第一帧,原因是 vsync 信号来的时候,第二帧没有准备好(也有可能是vsync信号快来的时候才开始绘制,所以即准备时间短也有可能造成这种现象)如果这种现象经常发生的话用户就可以感觉得到页面会有一点卡顿。如果绘制也能和 vsync 信号一致的话这种类型的问题就可以解决了如下图:
如果每一次信号来的时候,页面开始绘制,如果页面优化的非常好,每次都能在16ms内完成页面就可以非常流畅了。那么有个问题是requestLayout() 发起绘制是随时可以发起的,那么android系统是怎么做的才能达到这样的效果?Android 系统服务有一个类 Choreographer ,往其内部发送一个消息,这个消息最快也要等到下一个 vsync 信号来的时候才能触发,相当于UI绘制的节奏完全由 Choreographer 来控制。
我们从客户端发起刷新 UI 重绘的方法 ViewRootImpl 的 requestLayout() 开始。
@Override
public void requestLayout() {
// 检查线程
checkThread();
scheduleTraversals();
}
void scheduleTraversals() {
// 往线程的消息队列里面插入了一个 SyncBarrier 消息
// Barrier 是屏障的意思 消息队列中插入该屏障消息以后,普通消息就停止处理等待它处理完成。
// 但是屏障对异步消息是没有影响的
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 往 mChoreographer 中插入了一个 postCallback
// mChoreographer = Choreographer.getInstance(); 他是从 sThreadInstance threadLocal 获取的 并不是单例模式,所以在不同的线程取出来的是不用的 mChoreographer 对象
// mChoreographer 是在 viewRootImpl 的构造器中创建的
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
上面方法先将线程的消息队列中插入一个屏障消息,让普通消息先停止,先执行屏障消息后在执行其他消息
然后通过 mChoreographer 发送了一个 callback 传入 mTraversalRunnable runnable 等待 async 信号来的时候执行
如果多次调用 requestLayout 会怎么样。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
首次调用后 mTraversalScheduled 会设置成 true ,防止多次调用。那么什么时候设置回 false 的呢,答案是在传入到 mChoreographer 的 runnable mTraversalRunnable中,也就是下一次信号来的时候执行 runnable 设置成 false。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
// 将 mTraversalScheduled = false; 设置成 false
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
}
}
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType,Runnable action, Object token, long delayMillis){
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
// 最终调用了 postCallbackDelayedInternal 方法
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
// 根据不同的 callbackType 插入到对应的单链表中,然后通过 dueTime 进行排序
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);
}
}
}
private void scheduleFrameLocked(long now) {
// 如果当前线程就是 Choreographer 的工作线程,直接调用 scheduleVsyncLocked()
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
// 否则发送 mHandler 到Choreographer的工作线程的 queue 的最前面
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
// 设置成异步消息
msg.setAsynchronous(true);
// 设置到最前面,当信号来时第一个执行
mHandler.sendMessageAtFrontOfQueue(msg);
}
}
scheduleFrameLocked() 方法就是判断线程,如果当前就是 Choreographer 工作线程则直接 发送 mDisplayEventReceiver.scheduleVsync(); 如果不是则发送 Hander 到工作线程中。当 Vsync 信号来的时候 surfaceFlinger 第一时间通知 Choreographer 刷新。
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
// 。。。
mTimestampNanos = timestampNanos;
mFrame = frame;
// this 是把自己传进去了,到时候 mHandler 发送消息执行的 run 方法就是下面的 方法
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
// 发送时带上时间戳 到时间再执行 run()
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
}
上面方法最终会执行到 run() doFrame 的函数如下
void doFrame(long frameTimeNanos, int frame) {
// 第一阶段
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
// 计算一下晚了多久
final long jitterNanos = startNanos - frameTimeNanos;
// 如果晚的时间超过一定时间
if (jitterNanos >= mFrameIntervalNanos) {
// 计算一下晚了多少帧
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
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;
}
// 第二阶段 处理 callback
try {
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);、
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
}
上面第二阶段 执行 doCallbacks 根据不同类型,执行不同的 callback,callback 是有时间戳的 只有时间到了才会去回调。
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 取出到了时间的 callback
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
// .....
}
try {
for (CallbackRecord c = callbacks; c != null; c = c.next) {
// 执行 run 方法
c.run(frameTimeNanos);
}
}
}
doCallbacks 就是根据类型取出到了时间的 callback ,然后执行它的 run 方法。之前讲过传入的 callback run 执行的是 doTraversal(); ,它内部执行了 performTraversals(); 函数,performTraversals(); 就是真正的执行绘制的方法。之后就是执行 performMeasure() ,performLayout() performDraw() 了。
view调用了 requestLayout() 其实是在 choreographer 中添加了一个 callback 到队列中,choreographer 像 SurfaceFlinger 请求下一个 vsync 信号,当信号来了 SurfaceFlinger 通过 post 发送通知给 choreographer ,choreographer 在获取消息队列中的消息,执行 run 调用 performTraversal() 。
private void scheduleVsyncLocked() {
// private static native void nativeScheduleVsync(long receiverPtr);
mDisplayEventReceiver.scheduleVsync();
}
这个方法执行到的是 native 层的 DisplayEventReceiver 的 scheduleVsync() 函数。