Android屏幕刷新——源码分析
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方法:
@Overridepublic 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,即上面设置的同步屏障 /* 前方高能!!!下面就是内存屏障真正起作用的地方了。 当发现这个内存屏障后,下面的循环就会遍历Message链表,忽略所有的同步消息,直到找到第一个异步消息。后面会将这个异步消息出队并返回。 */ 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与图形系统中的撕裂、双缓冲、三缓冲浅析
更多相关文章
- Android消息循环分析
- Android异步消息处理之Thread+Handler
- Android线程与异步消息处理机制
- android的消息处理机制(Looper,Handler,Message)
- Android的消息机制分析
- android中实现消息推送
- Android消息传递之EventBus 3.0使用详解
- Android中的消息推送
- Android源码探究:Android Java层消息机制完全解析