<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android Vsync與UI刷新原理分析

          共 9789字,需瀏覽 20分鐘

           ·

          2022-11-22 15:15

          和你一起終身學(xué)習(xí),這里是程序員Android

          經(jīng)典好文推薦,通過閱讀本文,您將收獲以下知識點(diǎn):

          從UI控件內(nèi)容更改到被重新繪制到屏幕上,這中間到底經(jīng)歷了什么?另外,連續(xù)兩次setTextView到底會(huì)觸發(fā)幾次UI重繪呢?為什么Android APP的幀率最高是60FPS呢,這就是本文要討論的內(nèi)容。

          以電影為例,動(dòng)畫至少要達(dá)到24FPS,才能保證畫面的流暢性,低于這個(gè)值,肉眼會(huì)感覺到卡頓。在手機(jī)上,這個(gè)值被調(diào)整到60FPS,增加絲滑度,這也是為什么有個(gè)(1000/60)16ms的指標(biāo),一般而言目前的Android系統(tǒng)最高FPS也就是60,它是通過了一個(gè)VSYNC來保證每16ms最多繪制一幀。簡而言之:UI必須至少等待16ms的間隔才會(huì)繪制下一幀,所以連續(xù)兩次setTextView只會(huì)觸發(fā)一次重繪。下面來具體看一下UI的重繪流程。

          UI刷新流程示意

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

          從我們的代碼端來看如下:setText最終調(diào)用invalidate申請重繪,最后會(huì)通過ViewParent遞歸到ViewRootImpl的invalidate,請求VSYNC,在請求VSYNC的時(shí)候,會(huì)添加一個(gè)同步柵欄,防止UI線程中同步消息執(zhí)行,這樣做為了加快VSYNC的響應(yīng)速度,如果不設(shè)置,VSYNC到來的時(shí)候,正在執(zhí)行一個(gè)同步消息,那么UI更新的Task就會(huì)被延遲執(zhí)行,這是Android的Looper跟MessageQueue決定的。

          APP端觸發(fā)重繪,申請VSYNC流程示意

          image

          等到VSYNC到來后,會(huì)移除同步柵欄,并率先開始執(zhí)行當(dāng)前幀的處理,調(diào)用邏輯如下

          VSYNC回來流程示意

          image

          doFrame執(zhí)行UI繪制的示意圖

          image

          UI刷新源碼跟蹤

          同TextView類似,View內(nèi)容改變一般都會(huì)調(diào)用invalidate觸發(fā)視圖重繪,這中間經(jīng)歷了什么呢?View會(huì)遞歸的調(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會(huì)調(diào)用scheduleTraversals準(zhǔn)備重繪,但是,重繪一般不會(huì)立即執(zhí)行,而是往Choreographer的Choreographer.CALLBACK_TRAVERSAL隊(duì)列中添加了一個(gè)mTraversalRunnable,同時(shí)申請VSYNC,這個(gè)mTraversalRunnable要一直等到申請的VSYNC到來后才會(huì)被執(zhí)行,如下:

          ViewRootImpl.java

           // 將UI繪制的mTraversalRunnable加入到下次垂直同步信號到來的等待callback中去
          // mTraversalScheduled用來保證本次Traversals未執(zhí)行前,不會(huì)要求遍歷兩邊,浪費(fèi)16ms內(nèi),不需要繪制兩次
          void scheduleTraversals() {
          if (!mTraversalScheduled) {
          mTraversalScheduled = true;
          // 防止同步柵欄,同步柵欄的意思就是攔截同步消息
          mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
          // postCallback的時(shí)候,順便請求vnsc垂直同步信號scheduleVsyncLocked
          mChoreographer.postCallback(
          Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
          <!--添加一個(gè)處理觸摸事件的回調(diào),防止中間有Touch事件過來-->
          if (!mUnbufferedInputDispatch) {
          scheduleConsumeBatchedInput();
          }
          notifyRendererOfFramePending();
          pokeDrawLockIfNeeded();
          }
          }

          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);
          }
          }
          }

          scheduleTraversals利用mTraversalScheduled保證,在當(dāng)前的mTraversalRunnable未被執(zhí)行前,scheduleTraversals不會(huì)再被有效調(diào)用,也就是Choreographer.CALLBACK_TRAVERSAL理論上應(yīng)該只有一個(gè)mTraversalRunnable的Task。mChoreographer.postCallback將mTraversalRunnable插入到CallBack之后,會(huì)接著調(diào)用scheduleFrameLocked請求Vsync同步信號

          // mFrameScheduled保證16ms內(nèi),只會(huì)申請一次垂直同步信號
          // scheduleFrameLocked可以被調(diào)用多次,但是mFrameScheduled保證下一個(gè)vsync到來之前,不會(huì)有新的請求發(fā)出
          // 多余的scheduleFrameLocked調(diào)用被無效化
          private void scheduleFrameLocked(long now) {
          if (!mFrameScheduled) {
          mFrameScheduled = true;
          if (USE_VSYNC) {

          if (isRunningOnLooperThreadLocked()) {
          scheduleVsyncLocked();
          } else {
          // 因?yàn)閕nvalid已經(jīng)有了同步柵欄,所以必須mFrameScheduled,消息才能被UI線程執(zhí)行
          Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
          msg.setAsynchronous(true);
          mHandler.sendMessageAtFrontOfQueue(msg);
          }
          }
          }
          }

          scheduleFrameLocked跟上一個(gè)scheduleTraversals類似,也采用了利用mFrameScheduled來保證:在當(dāng)前申請的VSYNC到來之前,不會(huì)再去請求新的VSYNC,因?yàn)?6ms內(nèi)申請兩個(gè)VSYNC沒意義。再VSYNC到來之后,Choreographer利用Handler將FrameDisplayEventReceiver封裝成一個(gè)異步Message,發(fā)送到UI線程的MessageQueue,

            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;
          }
          <!--如果上一個(gè)vsync同步信號沒執(zhí)行,那就不應(yīng)該相應(yīng)下一個(gè)(可能是其他線程通過某種方式請求的)-->
          if (mHavePendingVsync) {
          Log.w(TAG, "Already have a pending vsync event. There should only be "
          + "one at a time.");
          } else {
          mHavePendingVsync = true;
          }
          <!--timestampNanos其實(shí)是本次vsync產(chǎn)生的時(shí)間,從服務(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其實(shí)就是本次Vynsc同步信號到來的時(shí)候,但是執(zhí)行這個(gè)消息的時(shí)候,可能延遲了-->
          doFrame(mTimestampNanos, mFrame);
          }
          }

          之所以封裝成異步Message,是因?yàn)榍懊嫣砑恿艘粋€(gè)同步柵欄,同步消息不會(huì)被執(zhí)行。UI線程被喚起,取出該消息,最終調(diào)用doFrame進(jìn)行UI刷新重繪

          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;
          <!--檢查是否因?yàn)檠舆t執(zhí)行掉幀,每大于16ms,就多掉一幀-->
          if (jitterNanos >= mFrameIntervalNanos) {
          final long skippedFrames = jitterNanos / mFrameIntervalNanos;
          <!--跳幀,其實(shí)就是上一次請求刷新被延遲的時(shí)間,但是這里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的真正有效時(shí)間戳-->
          frameTimeNanos = startNanos - lastFrameOffset;
          }

          if (frameTimeNanos < mLastFrameTimeNanos) {
          <!--這種情況一般是生成vsync的機(jī)制出現(xiàn)了問題,那就再申請一次-->
          scheduleVsyncLocked();
          return;
          }
          <!--intendedFrameTimeNanos是本來要繪制的時(shí)間戳,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);
          <!--處理動(dòng)畫-->
          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也采用了一個(gè)boolean遍歷mFrameScheduled保證每次VSYNC中,只執(zhí)行一次,可以看到,為了保證16ms只執(zhí)行一次重繪,加了好多次層保障。doFrame里除了UI重繪,其實(shí)還處理了很多其他的事,比如檢測VSYNC被延遲多久執(zhí)行,掉了多少幀,處理Touch事件(一般是MOVE),處理動(dòng)畫,以及UI,當(dāng)doFrame在處理Choreographer.CALLBACK_TRAVERSAL的回調(diào)時(shí)(mTraversalRunnable),才是真正的開始處理View重繪:

            final class TraversalRunnable implements Runnable {
          @Override
          public void run() {
          doTraversal();
          }
          }

          回到ViewRootImpl調(diào)用doTraversal進(jìn)行View樹遍歷,

          // 這里是真正執(zhí)行了,
          void doTraversal() {
          if (mTraversalScheduled) {
          mTraversalScheduled = false;
          <!--移除同步柵欄,只有重繪才設(shè)置了柵欄,說明重繪的優(yōu)先級還是挺高的,所有的同步消息必須讓步-->
          mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
          performTraversals();
          }
          }

          doTraversal會(huì)先將柵欄移除,然后處理performTraversals,進(jìn)行測量、布局、繪制,提交當(dāng)前幀給SurfaceFlinger進(jìn)行圖層合成顯示。以上多個(gè)boolean變量保證了每16ms最多執(zhí)行一次UI重繪,這也是目前Android存在60FPS上限的原因。

          注: VSYNC同步信號需要用戶主動(dòng)去請求才會(huì)收到,并且是單次有效。

          UI局部重繪

          某一個(gè)View重繪刷新,并不會(huì)導(dǎo)致所有View都進(jìn)行一次measure、layout、draw,只是這個(gè)待刷新View鏈路需要調(diào)整,剩余的View可能不需要浪費(fèi)精力再來一遍,反應(yīng)再APP側(cè)就是:不需要再次調(diào)用所有ViewupdateDisplayListIfDirty構(gòu)建RenderNode渲染Op樹,如下

          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要客戶端主動(dòng)申請,才會(huì)有

          • 有VSYNC到來才會(huì)刷新

          • UI沒更改,不會(huì)請求VSYNC也就不會(huì)刷新

          • UI局部重繪其實(shí)只是省去了再次構(gòu)建硬件加速用的DrawOp樹(復(fù)用上衣幀的)

          原文鏈接:https://www.jianshu.com/p/10db590ed9a6

          友情推薦:

          Android 開發(fā)干貨集錦

          至此,本篇已結(jié)束。轉(zhuǎn)載網(wǎng)絡(luò)的文章,小編覺得很優(yōu)秀,歡迎點(diǎn)擊閱讀原文,支持原創(chuàng)作者,如有侵權(quán),懇請聯(lián)系小編刪除,歡迎您的建議與指正。同時(shí)期待您的關(guān)注,感謝您的閱讀,謝謝!

          點(diǎn)擊閱讀原文,為大佬點(diǎn)贊!

          瀏覽 46
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产成人黄 | 青青草视频免费看 | 天天干天天摸天天 | 亚洲色噜噜噜 | 最新免费一区二区三区 |