Vsync 信號機(jī)制和 UI 刷新流程
前言
屏幕刷新幀率不穩(wěn)定,掉幀嚴(yán)重,無法保證每秒60幀,導(dǎo)致屏幕畫面撕裂;
今天我們來講解下VSYNC機(jī)制和UI刷新流程
一、 Vsync信號詳解
1、屏幕刷新相關(guān)知識點
- 屏幕刷新頻率:一秒內(nèi)屏幕刷新的次數(shù)(一秒內(nèi)顯示了多少幀的圖像),單位 Hz(赫茲),如常見的 60 Hz。刷新頻率取決于硬件的固定參數(shù)(不會變的);
 - 逐行掃:顯示器并不是一次性將畫面顯示到屏幕上,而是從左到右邊,從上到下逐行掃描,順序顯示整屏的一個個像素點,不過這一過程快到人眼無法察覺到變化。以 60 Hz 刷新率的屏幕為例,這一過程即 1000 / 60 ≈ 16ms;
 - 幀率:表示 GPU 在一秒內(nèi)繪制操作的幀數(shù),單位 fps。例如在電影界采用 24 幀的速度足夠使畫面運行的非常流暢。而 Android 系統(tǒng)則采用更加流程的 60 fps,即每秒鐘GPU最多繪制 60 幀畫面。幀率是動態(tài)變化的,例如當(dāng)畫面靜止時,GPU 是沒有繪制操作的,屏幕刷新的還是buffer中的數(shù)據(jù),即GPU最后操作的幀數(shù)據(jù);
 - 屏幕流暢度:即以每秒60幀(每幀16.6ms)的速度運行,也就是60fps,并且沒有任何延遲或者掉幀;
 - FPS:每秒的幀數(shù);
 - 丟幀:在16.6ms完成工作卻因各種原因沒做完,占了后n個16.6ms的時間,相當(dāng)于丟了n幀;
 
2、VSYNC機(jī)制
VSync機(jī)制:Android系統(tǒng)每隔16ms發(fā)出VSYNC信號,觸發(fā)對UI進(jìn)行渲染,VSync是Vertical Synchronization(垂直同步)的縮寫,是一種在PC上很早就廣泛使用的技術(shù),可以簡單的把它認(rèn)為是一種定時中斷。而在Android 4.1(JB)中已經(jīng)開始引入VSync機(jī)制;
VSync機(jī)制下的繪制過程;CPU/GPU接收vsync信號,Vsync每16ms一次,那么在每次發(fā)出Vsync命令時,CPU都會進(jìn)行刷新的操作。也就是在每個16ms的第一時間,CPU就會響應(yīng)Vsync的命令,來進(jìn)行數(shù)據(jù)刷新的動作。CPU和GPU的刷新時間,和Display的FPS是一致的。因為只有到發(fā)出Vsync命令的時候,CPU和GPU才會進(jìn)行刷新或顯示的動作。CPU/GPU接收vsync信號提前準(zhǔn)備下一幀要顯示的內(nèi)容,所以能夠及時準(zhǔn)備好每一幀的數(shù)據(jù),保證畫面的流暢;
可見vsync信號沒有提醒CPU/GPU工作的情況下,在第一個16ms之內(nèi),一切正常。然而在第二個16ms之內(nèi),幾乎是在時間段的最后CPU才計算出了數(shù)據(jù),交給了Graphics Driver,導(dǎo)致GPU也是在第二段的末尾時間才進(jìn)行了繪制,整個動作延后到了第三段內(nèi)。從而影響了下一個畫面的繪制。這時會出現(xiàn)Jank(閃爍,可以理解為卡頓或者停頓)。這時候CPU和GPU可能被其他操作占用了,這就是卡頓出現(xiàn)的原因;
二、UI刷新原理流程
1、VSYNC流程示意
當(dāng)我們通過setText改變TextView內(nèi)容后,UI界面不會立刻改變,APP端會先向VSYNC服務(wù)請求,等到下一次VSYNC信號觸發(fā)后,APP端的UI才真的開始刷新,基本流程如下:
setText最終調(diào)用invalidate申請重繪,最后會通過ViewParent遞歸到ViewRootImpl的invalidate,請求VSYNC,在請求VSYNC的時候,會添加一個同步柵欄,防止UI線程中同步消息執(zhí)行,這樣做為了加快VSYNC的響應(yīng)速度,如果不設(shè)置,VSYNC到來的時候,正在執(zhí)行一個同步消息;
2、view的invalidate
View會遞歸的調(diào)用父容器的invalidateChild,逐級回溯,最終走到ViewRootImpl的invalidate
- View.java
 - void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
 - boolean fullInvalidate) {
 - // 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);
 - }
 - ViewRootImpl.java
 - void invalidate() {
 - mDirty.set(0, 0, mWidth, mHeight);
 - if (!mWillDrawSoon) {
 - scheduleTraversals();
 - }
 - }
 
ViewRootImpl會調(diào)用scheduleTraversals準(zhǔn)備重繪,但是,重繪一般不會立即執(zhí)行,而是往Choreographer的Choreographer.CALLBACK_TRAVERSAL隊列中添加了一個mTraversalRunnable,同時申請VSYNC,這個mTraversalRunnable要一直等到申請的VSYNC到來后才會被執(zhí)行;
3、scheduleTraversals
- ViewRootImpl.java
 - // 將UI繪制的mTraversalRunnable加入到下次垂直同步信號到來的等待callback中去
 - // mTraversalScheduled用來保證本次Traversals未執(zhí)行前,不會要求遍歷兩邊,浪費16ms內(nèi),不需要繪制兩次
 - void scheduleTraversals() {
 - if (!mTraversalScheduled) {
 - mTraversalScheduled = true;
 - // 防止同步柵欄,同步柵欄的意思就是攔截同步消息
 - mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
 - // postCallback的時候,順便請求vnsc垂直同步信號scheduleVsyncLocked
 - mChoreographer.postCallback(
 - Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
 - <!--添加一個處理觸摸事件的回調(diào),防止中間有Touch事件過來-->
 - if (!mUnbufferedInputDispatch) {
 - scheduleConsumeBatchedInput();
 - }
 - notifyRendererOfFramePending();
 - pokeDrawLockIfNeeded();
 - }
 - }
 
4、申請VSYNC同步信號
- Choreographer.java
 - private void postCallbackDelayedInternal(int callbackType,
 - Object action, Object token, long delayMillis) {
 - synchronized (mLock) {
 - final long now = SystemClock.uptimeMillis();
 - final long dueTime = now + delayMillis;
 - mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
 - if (dueTime <= now) {
 - <!--申請VSYNC同步信號-->
 - scheduleFrameLocked(now);
 - }
 - }
 - }
 
5、scheduleFrameLocked
- // mFrameScheduled保證16ms內(nèi),只會申請一次垂直同步信號
 - // scheduleFrameLocked可以被調(diào)用多次,但是mFrameScheduled保證下一個vsync到來之前,不會有新的請求發(fā)出
 - // 多余的scheduleFrameLocked調(diào)用被無效化
 - private void scheduleFrameLocked(long now) {
 - if (!mFrameScheduled) {
 - mFrameScheduled = true;
 - if (USE_VSYNC) {
 - if (isRunningOnLooperThreadLocked()) {
 - scheduleVsyncLocked();
 - } else {
 - // 因為invalid已經(jīng)有了同步柵欄,所以必須mFrameScheduled,消息才能被UI線程執(zhí)行
 - Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
 - msg.setAsynchronous(true);
 - mHandler.sendMessageAtFrontOfQueue(msg);
 - }
 - }
 - }
 - }
 
- 在當(dāng)前申請的VSYNC到來之前,不會再去請求新的VSYNC,因為16ms內(nèi)申請兩個VSYNC沒意義;
 - 再VSYNC到來之后,Choreographer利用Handler將FrameDisplayEventReceiver封裝成一個異步Message,發(fā)送到UI線程的MessageQueue;
 
6、FrameDisplayEventReceiver
- private final class FrameDisplayEventReceiver extends DisplayEventReceiver
 - implements Runnable {
 - private boolean mHavePendingVsync;
 - private long mTimestampNanos;
 - private int mFrame;
 - public FrameDisplayEventReceiver(Looper looper) {
 - super(looper);
 - }
 - @Override
 - public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
 - long now = System.nanoTime();
 - if (timestampNanos > now) {
 - <!--正常情況,timestampNanos不應(yīng)該大于now,一般是上傳vsync的機(jī)制出了問題-->
 - timestampNanos = now;
 - }
 - <!--如果上一個vsync同步信號沒執(zhí)行,那就不應(yīng)該相應(yīng)下一個(可能是其他線程通過某種方式請求的)-->
 - if (mHavePendingVsync) {
 - Log.w(TAG, "Already have a pending vsync event. There should only be "
 - + "one at a time.");
 - } else {
 - mHavePendingVsync = true;
 - }
 - <!--timestampNanos其實是本次vsync產(chǎn)生的時間,從服務(wù)端發(fā)過來-->
 - mTimestampNanos = timestampNanos;
 - mFrame = frame;
 - Message msg = Message.obtain(mHandler, this);
 - <!--由于已經(jīng)存在同步柵欄,所以VSYNC到來的Message需要作為異步消息發(fā)送過去-->
 - msg.setAsynchronous(true);
 - mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
 - }
 - @Override
 - public void run() {
 - mHavePendingVsync = false;
 - <!--這里的mTimestampNanos其實就是本次Vynsc同步信號到來的時候,但是執(zhí)行這個消息的時候,可能延遲了-->
 - doFrame(mTimestampNanos, mFrame);
 - }
 - }
 
- 之所以封裝成異步Message,是因為前面添加了一個同步柵欄,同步消息不會被執(zhí)行;
 - UI線程被喚起,取出該消息,最終調(diào)用doFrame進(jìn)行UI刷新重繪;
 
7、doFrame
- void doFrame(long frameTimeNanos, int frame) {
 - final long startNanos;
 - synchronized (mLock) {
 - <!--做了很多東西,都是為了保證一次16ms有一次垂直同步信號,有一次input 、刷新、重繪-->
 - if (!mFrameScheduled) {
 - return; // no work to do
 - }
 - long intendedFrameTimeNanos = frameTimeNanos;
 - startNanos = System.nanoTime();
 - final long jitterNanos = startNanos - frameTimeNanos;
 - <!--檢查是否因為延遲執(zhí)行掉幀,每大于16ms,就多掉一幀-->
 - if (jitterNanos >= mFrameIntervalNanos) {
 - final long skippedFrames = jitterNanos / mFrameIntervalNanos;
 - <!--跳幀,其實就是上一次請求刷新被延遲的時間,但是這里skippedFrames為0不代表沒有掉幀-->
 - if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
 - <!--skippedFrames很大一定掉幀,但是為 0,去并非沒掉幀-->
 - Log.i(TAG, "Skipped " + skippedFrames + " frames! "
 - + "The application may be doing too much work on its main thread.");
 - }
 - final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
 - <!--開始doFrame的真正有效時間戳-->
 - frameTimeNanos = startNanos - lastFrameOffset;
 - }
 - if (frameTimeNanos < mLastFrameTimeNanos) {
 - <!--這種情況一般是生成vsync的機(jī)制出現(xiàn)了問題,那就再申請一次-->
 - scheduleVsyncLocked();
 - return;
 - }
 - <!--intendedFrameTimeNanos是本來要繪制的時間戳,frameTimeNanos是真正的,可以在渲染工具中標(biāo)識延遲VSYNC多少-->
 - mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
 - <!--移除mFrameScheduled判斷,說明處理開始了,-->
 - mFrameScheduled = false;
 - <!--更新mLastFrameTimeNanos-->
 - mLastFrameTimeNanos = frameTimeNanos;
 - }
 - try {
 - <!--真正開始處理業(yè)務(wù)-->
 - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
 - <!--處理打包的move事件-->
 - 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);
 - }
 - }
 
- doTraversal會先將柵欄移除,然后處理performTraversals,進(jìn)行測量、布局、繪制,提交當(dāng)前幀給SurfaceFlinger進(jìn)行圖層合成顯示;
 - 以上多個boolean變量保證了每16ms最多執(zhí)行一次UI重繪;
 
9、UI局部重繪
View重繪刷新,并不會導(dǎo)致所有View都進(jìn)行一次measure、layout、draw,只是這個待刷新View鏈路需要調(diào)整,剩余的View可能不需要浪費精力再來一遍;
- View.java
 - public RenderNode updateDisplayListIfDirty() {
 - final RenderNode renderNode = mRenderNode;
 - ...
 - if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
 - || !renderNode.isValid()
 - || (mRecreateDisplayList)) {
 - <!--失效了,需要重繪-->
 - } else {
 - <!--依舊有效,無需重繪-->
 - mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
 - mPrivateFlags &= ~PFLAG_DIRTY_MASK;
 - }
 - return renderNode;
 - }
 
10、繪制總結(jié)
- android最高60FPS,是VSYNC及決定的,每16ms最多一幀;
 - VSYNC要客戶端主動申請,才會有;
 - 有VSYNC到來才會刷新;
 - UI沒更改,不會請求VSYNC也就不會刷新;
 
總結(jié)
關(guān)于繪制還有很多知識點,后面會總結(jié)陸續(xù)發(fā)出來的;


















 
 
 










 
 
 
 