欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android的VSYNC機制和UI刷新流程示例詳解

 更新時間:2022年12月09日 14:11:29   作者:Android開發(fā)編程  
這篇文章主要為大家介紹了Android的VSYNC機制和UI刷新流程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

屏幕刷新幀率不穩(wěn)定,掉幀嚴重,無法保證每秒60幀,導致屏幕畫面撕裂;

今天我們來講解下VSYNC機制和UI刷新流程

一、 Vsync信號詳解

1、屏幕刷新相關知識點

  • 屏幕刷新頻率: 一秒內(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)變化的,例如當畫面靜止時,GPU 是沒有繪制操作的,屏幕刷新的還是buffer中的數(shù)據(jù),即GPU最后操作的幀數(shù)據(jù);
  • 屏幕流暢度:即以每秒60幀(每幀16.6ms)的速度運行,也就是60fps,并且沒有任何延遲或者掉幀;
  • FPS:每秒的幀數(shù);
  • 丟幀:在16.6ms完成工作卻因各種原因沒做完,占了后n個16.6ms的時間,相當于丟了n幀;

2、VSYNC機制

VSync機制: Android系統(tǒng)每隔16ms發(fā)出VSYNC信號,觸發(fā)對UI進行渲染,VSync是Vertical Synchronization(垂直同步)的縮寫,是一種在PC上很早就廣泛使用的技術,可以簡單的把它認為是一種定時中斷。而在Android 4.1(JB)中已經(jīng)開始引入VSync機制;

VSync機制下的繪制過程;CPU/GPU接收vsync信號,Vsync每16ms一次,那么在每次發(fā)出Vsync命令時,CPU都會進行刷新的操作。也就是在每個16ms的第一時間,CPU就會響應Vsync的命令,來進行數(shù)據(jù)刷新的動作。CPU和GPU的刷新時間,和Display的FPS是一致的。因為只有到發(fā)出Vsync命令的時候,CPU和GPU才會進行刷新或顯示的動作。CPU/GPU接收vsync信號提前準備下一幀要顯示的內(nèi)容,所以能夠及時準備好每一幀的數(shù)據(jù),保證畫面的流暢; 

可見vsync信號沒有提醒CPU/GPU工作的情況下,在第一個16ms之內(nèi),一切正常。然而在第二個16ms之內(nèi),幾乎是在時間段的最后CPU才計算出了數(shù)據(jù),交給了Graphics Driver,導致GPU也是在第二段的末尾時間才進行了繪制,整個動作延后到了第三段內(nèi)。從而影響了下一個畫面的繪制。這時會出現(xiàn)Jank(閃爍,可以理解為卡頓或者停頓)。這時候CPU和GPU可能被其他操作占用了,這就是卡頓出現(xiàn)的原因;

二、UI刷新原理流程

1、VSYNC流程示意

當我們通過setText改變TextView內(nèi)容后,UI界面不會立刻改變,APP端會先向VSYNC服務請求,等到下一次VSYNC信號觸發(fā)后,APP端的UI才真的開始刷新,基本流程如下:

setText最終調(diào)用invalidate申請重繪,最后會通過ViewParent遞歸到ViewRootImpl的invalidate,請求VSYNC,在請求VSYNC的時候,會添加一個同步柵欄,防止UI線程中同步消息執(zhí)行,這樣做為了加快VSYNC的響應速度,如果不設置,VSYNC到來的時候,正在執(zhí)行一個同步消息;

2、view的invalidate

View會遞歸的調(diào)用父容器的invalidateChild,逐級回溯,最終走到ViewRootImpl的invalidate

 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í)行,而是往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知識點在上個文章詳細介紹過;

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);
            }
        }  
    }
}
  • 在當前申請的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不應該大于now,一般是上傳vsync的機制出了問題-->
                timestampNanos = now;
            }
            <!--如果上一個vsync同步信號沒執(zhí)行,那就不應該相應下一個(可能是其他線程通過某種方式請求的)-->
              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)生的時間,從服務端發(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進行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的機制出現(xiàn)了問題,那就再申請一次-->
            scheduleVsyncLocked();
            return;
        }
          <!--intendedFrameTimeNanos是本來要繪制的時間戳,frameTimeNanos是真正的,可以在渲染工具中標識延遲VSYNC多少-->
        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
        <!--移除mFrameScheduled判斷,說明處理開始了,-->
        mFrameScheduled = false;
        <!--更新mLastFrameTimeNanos-->
        mLastFrameTimeNanos = frameTimeNanos;
    }
    try {
         <!--真正開始處理業(yè)務-->
        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);
    }
}
  • doFrame也采用了一個boolean遍歷mFrameScheduled保證每次VSYNC中,只執(zhí)行一次,可以看到,為了保證16ms只執(zhí)行一次重繪,加了好多次層保障;
  • doFrame里除了UI重繪,其實還處理了很多其他的事,比如檢測VSYNC被延遲多久執(zhí)行,掉了多少幀,處理Touch事件(一般是MOVE),處理動畫,以及UI;
  • 當doFrame在處理Choreographer.CALLBACK_TRAVERSAL的回調(diào)時(mTraversalRunnable),才是真正的開始處理View重繪;
  final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

回到ViewRootImpl調(diào)用doTraversal進行View樹遍歷;

8、doTraversal

// 這里是真正執(zhí)行了,
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        <!--移除同步柵欄,只有重繪才設置了柵欄,說明重繪的優(yōu)先級還是挺高的,所有的同步消息必須讓步-->
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        performTraversals();
    }
}
  • doTraversal會先將柵欄移除,然后處理performTraversals,進行測量、布局、繪制,提交當前幀給SurfaceFlinger進行圖層合成顯示;
  • 以上多個boolean變量保證了每16ms最多執(zhí)行一次UI重繪;

9、UI局部重繪

View重繪刷新,并不會導致所有View都進行一次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;
    }

繪制總結(jié)

  • android最高60FPS,是VSYNC及決定的,每16ms最多一幀;
  • VSYNC要客戶端主動申請,才會有;
  • 有VSYNC到來才會刷新;
  • UI沒更改,不會請求VSYNC也就不會刷新;

以上就是Android的VSYNC機制和UI刷新流程示例詳解的詳細內(nèi)容,更多關于Android VSYNC機制UI刷新的資料請關注腳本之家其它相關文章!

相關文章

  • Android實現(xiàn)微信登錄的示例代碼

    Android實現(xiàn)微信登錄的示例代碼

    微信登錄的實現(xiàn)與qq登錄類似。本文主要介紹了Android實現(xiàn)微信登錄,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Android App中用Handler實現(xiàn)ViewPager頁面的自動切換

    Android App中用Handler實現(xiàn)ViewPager頁面的自動切換

    這篇文章主要介紹了Android App中用Handler實現(xiàn)ViewPager頁面的自動切換的方法,類似于相冊自動播放,主要是切換后要提示當前頁面所在的位置,需要的朋友可以參考下
    2016-05-05
  • 聊一聊Android中的StateListAnimator

    聊一聊Android中的StateListAnimator

    這篇文章主要給大家介紹了關于Android中StateListAnimator的相關資料,文中通過示例代碼介紹的非常詳細,對各位Android開發(fā)者們具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-12-12
  • Android開發(fā)之登錄驗證實例教程

    Android開發(fā)之登錄驗證實例教程

    這篇文章主要介紹了Android開發(fā)之登錄驗證實現(xiàn)方法,包括發(fā)送數(shù)據(jù)、服務器端驗證、配置文件等,需要的朋友可以參考下
    2014-08-08
  • Android入門之實現(xiàn)自定義可復用的BaseAdapter

    Android入門之實現(xiàn)自定義可復用的BaseAdapter

    這篇文章主要為大家詳細介紹了Android如何構(gòu)建一個可復用的自定義BaseAdapter,文中的示例代碼講解詳細,對我們學習Android有一定的幫助,需要的可以參考一下
    2022-11-11
  • Android WebView 詳解及簡單實例

    Android WebView 詳解及簡單實例

    這篇文章主要介紹了Android WebView 詳解及簡單實例的相關資料,需要的朋友可以參考下
    2017-04-04
  • Android超詳細講解組件LinearLayout的使用

    Android超詳細講解組件LinearLayout的使用

    LinearLayout又稱作線性布局,是一種非常常用的布局。正如它的名字所描述的一樣,這個布局會將它所包含的控件在線性方向上依次排列。既然是線性排列,肯定就不僅只有一個方向,這里一般只有兩個方向:水平方向和垂直方向
    2022-03-03
  • android使用intent傳遞參數(shù)實現(xiàn)乘法計算

    android使用intent傳遞參數(shù)實現(xiàn)乘法計算

    這篇文章主要為大家詳細介紹了android使用intent傳遞參數(shù)實現(xiàn)乘法計算,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Android仿微信菜單(Menu)(使用C#和Java分別實現(xiàn))

    Android仿微信菜單(Menu)(使用C#和Java分別實現(xiàn))

    這篇文章主要介紹了Android仿微信菜單(Menu)(使用C#和Java分別實現(xiàn)),本文分別給出C#和Java版的運行效果及實現(xiàn)代碼,需要的朋友可以參考下
    2015-06-06
  • Android實現(xiàn)圖片點擊放大

    Android實現(xiàn)圖片點擊放大

    這篇文章主要為大家詳細介紹了Android實現(xiàn)圖片點擊放大,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-10-10

最新評論