Android Framework 學習(八):屏幕刷新機制


一、什么是屏幕刷新機制

屏幕的刷新包括三個步驟:

  • CPU 計算屏幕數據
  • GPU 進一步處理和緩存
  • Display 將緩存中(buffer)的屏幕數據顯示出來。
屏幕刷新機制包含以下幾點要素,需要我們了解和掌握:
  • View 發起刷新的操作時,最終是走到了 ViewRootImpl 的 scheduleTraversals() 里去,然后這個方法會將遍歷繪制 View 樹的操作 performTraversals() 封裝到 Runnable 里,傳給 Choreographer,以當前的時間戳放進一個 mCallbackQueue 隊列里,然后調用了 native 層的方法向底層注冊監聽下一個屏幕刷新信號事件。

  • 當下一個屏幕刷新信號發出的時候,如果對這個事件進行監聽,那么底層會回調 onVsync() 方法來通知。當 onVsync() 被回調時,會發一個 Message 到主線程,將后續的工作切到主線程來執行。切到主線程的工作就是去 mCallbackQueue 隊列里根據時間戳將之前放進去的 Runnable 取出來執行,而這些 Runnable 就是遍歷繪制 View 樹的操作 performTraversals()。遍歷操作完成后,就會去繪制那些需要刷新的 View。

  • 當我們調用了 invalidate(),requestLayout(),等之類刷新界面的操作時,並不是馬上就會執行這些刷新的操作,而是通過 ViewRootImpl 的 scheduleTraversals() 先向底層注冊監聽下一個屏幕刷新信號事件,然后等下一個屏幕刷新信號來的時候,才會去通過 performTraversals() 遍歷繪制 View 樹來執行這些刷新操作。

  • 導致界面刷新丟幀的原因有兩類:一是遍歷繪制 View 樹計算屏幕數據的時間超過了 16.6ms;二是,主線程一直在處理其他耗時的消息,導致遍歷繪制 View 樹的工作遲遲不能開始,從而超過了 16.6 ms 底層切換下一幀畫面的時機。第一個原因是因為我們寫的布局有問題,需要進行優化了。而第二個原因則是我們常說的避免在主線程中做耗時的任務。針對第二個原因,系統已經引入了同步屏障消息的機制,盡可能的保證遍歷繪制 View 樹的工作能夠及時進行,但仍沒辦法完全避免,所以我們還是得盡可能避免主線程耗時工作。

二、Choreographer機制

Choreographer機制,用於同Vsync機制配合,統一動畫、輸入和繪制的時機。

本節講解Choreographer機制主要是從繪制方面做闡述,界面的繪制要從ViewRootImpl的requestLayout開始說起,其源碼如下:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();     // 檢查是否在UI線程
            mLayoutRequested = true;  // 是否進行measure和layout布局
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 攔截同步消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 執行繪制操作
       mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

這里需要注意的地方有兩點:

  • postSyncBarrier()方法:Handler 的同步屏障,作用是可以攔截 Looper 對同步消息的獲取和分發,加入同步屏障之后Looper 只會獲取和處理異步消息,如果沒有異步消息那么就會進入阻塞狀態。通過同步屏障,就為UI繪制渲染處理操作的優先處理提供了基礎。
  • Choreographer:編舞者,統一動畫、輸入和繪制時機。

1. Choreographer 啟動邏輯

Choreographer的創建是在ViewRootImpl的構造函數中。

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mWindowSession = WindowManagerGlobal.getWindowSession();
    ...
    mChoreographer = Choreographer.getInstance();
    ...
}

下面是Choreographer的getInstance執行的代碼:

    /**
     * Gets the choreographer for the calling thread.  Must be called from
     * a thread that already has a {@link android.os.Looper} associated with it.
     *
     * @return The choreographer for this thread.
     * @throws IllegalStateException if the thread does not have a looper.
     */
    public static Choreographer getInstance() {
        return sThreadInstance.get();
    }

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

    private static volatile Choreographer mMainInstance;

可以看到每一個Looper線程都有自己的Choreographer,其他線程發送的回調只能運行在對應Choreographer所屬的Looper線程上。

這里我們再看一下Choreographer的構造函數:

    private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;

        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
        for (int i = 0; i <= CALLBACK_LAST; i++) {
            mCallbackQueues[i] = new CallbackQueue();
        }
        // b/68769804: For low FPS experiments.
        setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
    }

Choreographer類中有一個Looper和一個FrameHandler變量。變量USE_VSYNC用於表示系統是否是用了Vsync同步機制,該值是通過讀取系統屬性debug.choreographer.vsync來獲取的。如果系統使用了Vsync同步機制,則創建一個FrameDisplayEventReceiver對象用於請求並接收Vsync事件,最后Choreographer創建了一個大小為3的CallbackQueue隊列數組,用於保存不同類型的Callback。

不同類型的Callback包括如下4種:

CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT。

CallbackQueue是一個容量為4的數組,每一個元素作為頭指針,引出對應類型的鏈表,4種事件就是通過這4個鏈表來維護的。而FrameHandler中主要處理三類消息:

private final class FrameHandler extends Handler {
   public FrameHandler(Looper looper) {
       super(looper);
   }

   @Override
   public void handleMessage(Message msg) {
       switch (msg.what) {
           case MSG_DO_FRAME:
               doFrame(System.nanoTime(), 0);
               break;
           case MSG_DO_SCHEDULE_VSYNC:
               doScheduleVsync();   // 請求VSYNC信號
               break;
           case MSG_DO_SCHEDULE_CALLBACK:
               doScheduleCallback(msg.arg1);
               break;
       }
   }
}

2. Choreographer執行流程 

執行流程就要需要從下面的代碼開始說起:

// 執行繪制操作
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

最終會調到 postCallbackDelayedInternal 方法:

private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
   synchronized (mLock) {
       // 當前時間
       final long now = SystemClock.uptimeMillis();
       // 回調執行時間,為當前時間加上延遲的時間
       final long dueTime = now + delayMillis;
       // obtainCallbackLocked會將傳入的3個參數轉換為CallbackRecord,然后CallbackQueue根據回調類型將CallbackRecord添加到鏈表上。
       mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
       if (dueTime >= now) {
           // 如果delayMillis=0的話,dueTime=now,則會馬上執行
           scheduleFrameLocked(now);
       } else {
           // 如果dueTime > now,則發送一個what為MSG_DO_SCHEDULE_CALLBACK類型的定時消息,等時間到了再處理,其最終處理也是執行scheduleFrameLocked(long now)方法
           Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
           msg.arg1 = callbackType;
           msg.setAsynchronous(true);
           mHandler.sendMessageAtTime(msg, dueTime);
       }
   }
}

mCallbackQueues先把對應的callback添加到鏈表上來,然后判斷是否有延遲,如果沒有則會馬上執行scheduleFrameLocked,如果有,則發送一個what為MSG_DO_SCHEDULE_CALLBACK類型的定時消息,等時間到了再處理,其最終處理也是執行scheduleFrameLocked(long now)方法。

private void scheduleFrameLocked(long now) {
   if (!mFrameScheduled) {
       mFrameScheduled = true;
       if (USE_VSYNC) {
           // 如果使用了VSYNC,由系統值確定
           if (DEBUG_FRAMES) {
               Log.d(TAG, "Scheduling next frame on vsync.");
           }
           if (isRunningOnLooperThreadLocked()) {
               // 請求VSYNC信號,最終會調到Native層,Native處理完成后觸發FrameDisplayEventReceiver的onVsync回調,回調中最后也會調用doFrame(long frameTimeNanos, int frame)方法
               scheduleVsyncLocked();
           } else {
               // 在UI線程上直接發送一個what=MSG_DO_SCHEDULE_VSYNC的消息,最終也會調到scheduleVsyncLocked()去請求VSYNC信號
               Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg);
           }
       } else {
           // 沒有使用VSYNC
           final long nextFrameTime = Math.max(
                   mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
           if (DEBUG_FRAMES) {
               Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
           }
           // 直接發送一個what=MSG_DO_FRAME的消息,消息處理時調用doFrame(long frameTimeNanos, int frame)方法
           Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime);
       }
   }
}

判斷USE_VSYNC,如果使用了VSYNC,走scheduleVsyncLocked,即請求VSYNC信號,最終調用doFrame;如果沒使用VSYNC,則通過異步Message執行doFrame。

下面我們看一下doFrame的代碼:

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        if (!mFrameScheduled) {
            return; // mFrameScheduled=false,則直接返回。
        }
        long intendedFrameTimeNanos = frameTimeNanos; //原本計划的繪幀時間點
        startNanos = System.nanoTime();//保存起始時間
        //由於Vsync事件處理采用的是異步方式,因此這里計算消息發送與函數調用開始之間所花費的時間
        final long jitterNanos = startNanos - frameTimeNanos;
        //如果線程處理該消息的時間超過了屏幕刷新周期
        if (jitterNanos >= mFrameIntervalNanos) {
            //計算函數調用期間所錯過的幀數
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            //當掉幀個數超過30,則輸出相應log
            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; //對齊幀的時間間隔
        }
       //如果frameTimeNanos小於一個屏幕刷新周期,則重新請求VSync信號
        if (frameTimeNanos < mLastFrameTimeNanos) {
            scheduleVsyncLocked();
            return;
        }
        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
        mFrameScheduled = false;
        mLastFrameTimeNanos = frameTimeNanos;
    }
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        //分別回調CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL事件
        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);
    }
}

doCallBacks里的run方法執行了,也就是真正執行了View的繪制流程了。

3.Choreographer總結

1). Choreographer支持4種類型事件:輸入、繪制、動畫、提交,並通過postCallback在對應需要同步vsync進行刷新處進行注冊,等待回調。

2). Choreographer監聽底層Vsync信號,一旦接收到回調信號,則通過doFrame統一對java層4種類型事件進行回調。

三、屏幕繪制過程的流程圖

 

參考資料:

https://www.jianshu.com/p/0d00cb85fdf3

https://www.jianshu.com/p/bab0b454e39e


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM