Android屏幕刷新原理——源码分析
文章目录
- Android屏幕刷新原理——源码分析
- 概述
- VSync信号
- 三级缓冲
- 源码分析
- 消息队列的同步屏障
- 参考资料
概述
Android系统每16ms
(一般的安卓手机的FPS(每秒的帧数)是60)会请求一次VSync(垂直同步)信号,进行一次屏幕刷新。在请求到VSync信号后系统会向主线程发送一个异步消息,为了保证UI的流畅,系统使用了消息队列的同步屏障
来优先处理这个屏幕重绘的异步消息。
VSync信号
VSync是Vertical Synchronization的缩写,译作垂直同步,是一种在PC上已经很早就广泛使用的技术。垂直同步信号简单来说就是周期性的中断。
使用VSync信号的作用
强制帧率和显示器刷新频率同步。也就是让CPU和GPU在Choreographer(编舞者,名字起地还挺文艺)的协调下在VSync信号到来后进行一次屏幕刷新,即view的重绘。
没有使用VSync信号的情况:
可能因为CPU和GPU处理不协调、CPU或GPU处理任务过多等因素而导致丢帧现象发生。
使用VSync信号的情况
CPU或GPU处理任务过多还是会导致丢帧现象发生。
三级缓冲
Android4.0之后基本都是默认硬件加速,CPU跟GPU都是并发处理任务的,CPU处理完之后就完工,等下一个VSYNC到来就可以进行下一轮操作。也就是CPU、GPU、显示都会用到Buffer,VSYNC+双缓冲在理想情况下是没有问题的,但如果某个环节出现问题,那就不一样了如下(帧耗时超过16ms):
可以看到在第二个阶段,存在CPU资源浪费,为什么呢?双缓冲Surface只会提供两个Buffer,一个Buffer被DisPlay占用(SurfaceFlinger用完后不会释放当前的Buffer,只会释放旧的Buffer,直观的想一下,如果新Buffer生成受阻,那么肯定要保留一个备份给SF用,才能不阻碍合成显示,就必定要一直占用一个Buffer,新的Buffer来了才释放老的),另一个被GPU处理占用,所以,CPU就无法获取到Buffer处理当前UI,在Jank的阶段空空等待。一般出现这种场景都是连续的:比如复杂视觉效果每一帧可能需要20ms(CPU 8ms +GPU 12ms),GPU可能会一直超负荷,CPU跟GPU一直抢Buffer,这样带来的问题就是滚雪球似的掉帧,一直浪费,完全没有利用CPU与GPU并行处理的效率,成了串行处理。
解决的办法就是再增加一个Buffer给CPU用,让它提前忙起来,这样就能做到三方都有Buffer可用,CPU跟GPU不用争一个Buffer,真正实现并行处理。如下:
Android屏幕刷新本质上来说就是view周期性地重新绘制到屏幕上的过程,而与这个过程有着密切关系的就是View的invalidate方法,下面就深入源码分析这个方法背后的原理。
源码分析
View.invalidate()
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
if (skipInvalidate()) { //跳过不可见和不再执行动画的view
return;
}
// Reset content capture caches
mCachedContentCaptureSession = null;
//下面会对是否缓存做一些判断
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage); //会调用到ViewGroup的invalidateChild方法
}
// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
}
}
ViewGroup.onDescendantInvalidated(@NonNull View child, @NonNull View target)
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
...
if (mParent != null) {
mParent.onDescendantInvalidated(this, target);
}
}
可以看到,它会一直向上调用父View的onDescendantInvalidated方法,直到调用到ViewRootImpl的onDescendantInvalidated方法:
@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
// TODO: Re-enable after camera is fixed or consider targetSdk checking this
// checkThread();
if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
mIsAnimating = true;
}
invalidate();
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
ViewRootImpl.scheduleTraversals()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true; //这个变量保证下面的代码在一个垂直同步信号周期内只执行一次
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); //向主线程的消息队列中post一个同步屏障
//postCallback方法在队列中添加了一个mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//添加一个处理触摸事件的回调,防止中间有Touch事件过来
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
上面通过mChoreographer这个Choreographer对象(上面提到的编舞者)在队列中添加了一个mTraversalRunnable(这是一个Runnable对象,下面还会说道说道),同时请求并等待VSync信号。
Choreographer的postCallback方法
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);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//将传入的action这个Runnable封装后加入mCallbackQueues回调队列
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
//请求vsync信号,请求到vsync垂直同步信号后,会向主线程的消息队列发送一个异步消息,让主线程更新UI
scheduleFrameLocked(now);
}
...
}
}
//这是Choreographer中的回调队列
private final CallbackQueue[] mCallbackQueues;
Choreographer.scheduleFrameLocked(long now)
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true; //这里也是保证下面代码在一个垂直同步周期内仅执行一次
if (USE_VSYNC) {
//这里其实就是判断是否在主线程中
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
}
...
}
}
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync(); //其中通过一个native方法请求vsync信号
}
上面mDisplayEventReceiver是一个FrameDisplayEventReceiver对象,它的声明如下,可以看到,这是一个Runnable对象,当请求到vsync信号后,就会回调下面的onVsync方法。
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, long physicalDisplayId, int frame) {
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;
Message msg = Message.obtain(mHandler, this); //将这个Runnable封装到异步Message中,并发送到主线程的消息队列中
msg.setAsynchronous(true);
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) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
long intendedFrameTimeNanos = frameTimeNanos; //下一帧的预期时间
startNanos = System.nanoTime(); //系统当前时间
final long jitterNanos = startNanos - frameTimeNanos; //抖动时间,也就是vsync信号到来的时刻到即将进行UI重绘的时间
//如果这个抖动时间 >= 16ms就会导致丢帧
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;
}
//出现了某种错误,会重新请求VSync信号
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;
}
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
try {
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);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
//处理UI重绘的回调,会对ViewRootImpl.scheduleTraversals()方法中加入的mTraversalRunnable进行回调
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
TraversalRunnable
它是ViewRootImpl中的内部类,这就是上面ViewRootImpl.scheduleTraversals()方法中在回调队列中加入的mTraversalRunnable,直到上面的doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos)才会执行。
//上面队列中加入的mTraversalRunnable对象的声明如下:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//先将消息队列的同步屏障移除
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals(); //这就是ViewRootImpl的绘制方法,它会通知所有子view进行measure、layout和draw等方法进行重绘
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
消息队列的同步屏障
作用
其实就是提高异步消息的优先级,让消息队列优先取出异步消息交给相应线程的Handler执行。
实现原理
同步屏障的实现首先要向消息队列中加入一个没有设置target的Message对象
,也就是说这个Message持有的Handler对象为null,在下面的代码中可以看到:
//MessageQueue.postSyncBarrier()
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
//下面会按照when,也就是消息的时序,将这个Message对象插入链表相应位置
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
然后我们看看**MessageQueue.next()**这个方法
如果Message对象在发送到消息队列前调用了 Message.setAsynchronous(true)
这个方法,就表明这个消息是一个异步消息,下面MessageQueue的next方法就会优先取出队列中的异步消息。
Message next() {
...
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) { //如果消息头结点的target为null,即上面设置的同步屏障
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
}
...
}
}
同步屏障的应用
就是上面屏幕刷新时会开启主线程消息队列的同步屏障,让它优先取出异步的屏幕刷新消息并在主线程执行。这样做的主要目的应该就是提高UI流畅度。
参考资料
Android VSYNC与图形系统中的撕裂、双缓冲、三缓冲浅析