Android Choreographer 源碼分析

Choreographer 的做用主要是配合 Vsync ,給上層 App 的渲染提供一個穩定的 Message 處理的時機,也就是 Vsync 到來的時候 ,系統經過對 Vsync 信號週期的調整,來控制每一幀繪製操做的時機。目前大部分手機都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系統爲了配合屏幕的刷新頻率,將 Vsync 的週期也設置爲 16.6 ms,每一個 16.6 ms , Vsync 信號喚醒 Choreographer 來作 App 的繪製操做,這就是引入 Choreographer 的主要做用。瞭解 Choreographer 還能夠幫助 App 開發者知道程序每一幀運行的基本原理,也能夠加深對 Message、Handler、Looper、MessageQueue、Measure、Layout、Draw 的理解html

主線程運行機制的本質

在介紹 Choreographer 以前,咱們先理一下 Android 主線程運行的本質,其實就是 Message 的處理過程,咱們的各類操做,包括每一幀的渲染,手勢操做 ,都是經過 Message 的形式發給主線程的 MessageQueue ,MessageQueue 處理完消息繼續等下一個消息,以下圖所示java

MethodTrace 圖示android

Systrace 圖示併發

能夠發現,每一幀時間都是固定的。因此一旦一個 Message 的處理時間超過了 16.6ms 就會引發卡頓。關於如何發現卡頓,能夠參考文章:app

Choreographer 簡介

Choreographer 扮演 Android 渲染鏈路中承上啓下的角色less

  1. 承上:負責接收和處理 App 的各類更新消息和回調,等到 Vsync 到來的時候統一處理。好比集中處理 Input(主要是 Input 事件的處理) 、Animation(動畫相關)、Traversal(包括 measure、layout、draw 等操做) ,判斷卡頓掉幀狀況,記錄 CallBack 耗時等socket

  2. 啓下:負責請求和接收 Vsync 信號。接收 Vsync 事件回調(經過 FrameDisplayEventReceiver.onVsync );請求 Vsync(FrameDisplayEventReceiver.scheduleVsync) .ide

從上面能夠看出來, Choreographer 擔任的是一個工具人的角色,他之因此重要,是由於經過 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 這一套從上到下的機制,保證了 Android App 能夠以一個穩定的幀率運行(目前大部分是 60fps),減小幀率波動帶來的不適感。函數

Choreographer 的工做流程

  • Choreographer 初始化SurfaceFlinger 的 appEventThread 喚醒發送 Vsync ,Choreographer 回調 FrameDisplayEventReceiver.onVsync , 進入 Choreographer 的主處理函數 doFrame工具

    • 初始化 FrameHandler ,綁定 Looper

    • 初始化 FrameDisplayEventReceiver ,與 SurfaceFlinger 創建通訊用於接收和請求 Vsync

    • 初始化 CallBackQueues

  • Choreographer.doFrame 計算掉幀邏輯

  • Choreographer.doFrame 處理 Choreographer 的第一個 callback : input

  • Choreographer.doFrame 處理 Choreographer 的第二個 callback : animation

  • Choreographer.doFrame 處理 Choreographer 的第三個 callback : insets animation

  • Choreographer.doFrame 處理 Choreographer 的第四個 callback : traversalChoreographer.doFrame 處理 Choreographer 的第五個 callback : commit ?

    • traversal-draw 中 UIThread 與 RenderThread 同步數據

  • RenderThread 處理繪製數據,真正進行渲染

  • 將渲染好的 Buffer swap 給 SurfaceFlinger 進行合成

Choreographer 源碼分析

Choreographer 的單例初始化

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

這裏採用的是 ThreadLocal 來構造單例,這樣每一個線程都會有一個屬於本身的 choreographer 實例。

接下去看 choreographer 的構造函數

    private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
     // 這裏能夠發現只有在爲 true 的時候纔會使用 vsync 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)); }

這裏作了幾個初始化操做,根據Looper對象生成,Looper和線程是一對一的關係,對應上面說明裏的每一個線程對應一個Choreographer。

  • 初始化FrameHandler。接收處理消息。

  • 初始化FrameDisplayEventReceiver。FrameDisplayEventReceiver用來接收垂直同步脈衝,就是VSync信號,VSync信號是一個時間脈衝,通常爲60HZ,用來控制系統同步操做,怎麼同ChoreoGrapher一塊兒工做的,將在下文介紹。

  • 初始化mLastFrameTimeNanos(標記上一個frame的渲染時間)以及mFrameIntervalNanos(幀率,fps,通常手機上爲1s/60)。

  • 初始化CallbackQueue,callback隊列,將在下一幀開始渲染時回調。

接下去看看 FrameHandler 和 FrameDisplayEventReceiver 的結構。

    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();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }

看上面的代碼,就是一個簡單的Handler。處理3個類型的消息。

  • MSG_DO_FRAME:開始渲染下一幀的操做

  • MSG_DO_SCHEDULE_VSYNC:請求 Vsync 信號

  • MSG_DO_SCHEDULE_CALLBACK:請求執行 callback

下面再細分一下,分別詳細看一下這三個步驟是怎麼實現的。

FrameDisplayEventReceiver

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

        // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
        // the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
        // for the internal display implicitly.
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            // Post the vsync event to the Handler.
            // The idea is to prevent incoming vsync events from completely starving
            // the message queue.  If there are no messages in the queue with timestamps
            // earlier than the frame time, then the vsync event will be processed immediately.
            // Otherwise, messages that predate the vsync event will be handled first.
            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);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }
    }
FrameDisplayEventReceiver 繼承自 DisplayEventReceiver,同時也實現了Runnable 接口,是處於 Choreographer 中的私有內部類。當接收到底層的 VSync 信號開始處理 UI 過程。VSync 信號由 SurfaceFlinger 實現並定時發送。FrameDisplayEventReceiver 收到信號後,調用 onVsync 方法組織消息發送到主線程處理。這個消息主要內容就是 run 方法裏面的 doFrame 了,這裏 mTimestampNanos 是信號到來的時間參數。

那麼 FrameDisplayEventReceiver 是經過什麼方式在 Vsync 信號到來的時候回調 onVsync 呢?答案是 FrameDisplayEventReceiver 的初始化的時候,最終經過監聽文件句柄的形式,其對應的初始化流程以下

// android/view/Choreographer.java
private Choreographer(Looper looper, int vsyncSource) {
    mLooper = looper;
    mDisplayEventReceiver = USE_VSYNC
            ? new FrameDisplayEventReceiver(looper, vsyncSource)
            : null;
    ......
}
// android/view/Choreographer.java public FrameDisplayEventReceiver(Looper looper, int vsyncSource) { super(looper, vsyncSource); }
// android/view/DisplayEventReceiver.java public DisplayEventReceiver(Looper looper, int vsyncSource) { ...... mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, vsyncSource); }

nativeInit 後續的代碼能夠本身跟一下,能夠對照這篇文章和源碼,因爲篇幅比較多,這裏就不細說了。

簡單來講,FrameDisplayEventReceiver 的初始化過程當中,經過 BitTube (本質是一個 socket pair),來傳遞和請求 Vsync 事件,當 SurfaceFlinger 收到 Vsync 事件以後,經過 appEventThread 將這個事件經過 BitTube 傳給 DisplayEventDispatcher ,DisplayEventDispatcher 經過 BitTube 的接收端監聽到 Vsync 事件以後,回調 Choreographer.FrameDisplayEventReceiver.onVsync ,觸發開始一幀的繪製。

以下圖

ChoreoGrapher 的整體流程

FrameHandler 和 FrameDisplayEventReceiver 是怎麼工做的呢?ChoreoGrapher 的整體流程圖以下圖(拷貝的圖片):

 以上是整體的流程圖:

  1. PostCallBack or postFrameCallback發起添加回調,這個FrameCallBack將在下一幀被渲染時執行。

  2. AddToCallBackQueue,將 FrameCallBack 添加到回調隊列裏面,等待時機執行回調。每種類型的callback按照設置的執行時間(dueTime)順序排序分別保存在一個單鏈表中。

  3. 判斷 FrameCallBack設定的執行時間是否在當前時間以後,如果,發送 MSG_DO_SCHEDULE_CALLBACK 消息到主線程,安排執行doScheduleCallback,安排執行CallBack。不然直接跳到第4步。

  4. 執行 scheduleFrameLocked,安排執行下一幀。

  5. 判斷上一幀是否已經執行,若未執行,當前操做直接結束。若已經執行,根據狀況執行如下六、7步。

  6. 若使用垂直同步信號進行同步,則執行7.不然,直接跳到9。

  7. 若當前線程是UI線程,則經過執行scheduleVsyncLocked請求垂直同步信號。不然,送MSG_DO_SCHEDULE_VSYNC消息到主線程,安排執行doScheduleVsync,在主線程調用scheduleVsyncLocked。

  8. 收到垂直同步信號,調用FrameDisplayEventReceiver.onVsync(),發送消息到主線程,請求執行doFrame。

  9. 執行doFrame,渲染下一幀。

主要的工做在 doFrame 中,接下來咱們具體看看 doFrame 函數都幹了些什麼。
從名字看很容易理解 doFrame 函數就是開始進行下一幀的顯示工做。好了如下源代碼又來了,咱們一行一行分析一下吧。

doFrame

    @UnsupportedAppUsage
    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
       // 爲false, 說明還未開始
if (!mFrameScheduled) { return; // no work to do } if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) { mDebugPrintNextFrameTimeDelta = false; Log.d(TAG, "Frame time delta: " + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); } long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime();
       // 計算當前時間與 vsync 信號的時間差
final long jitterNanos = startNanos - frameTimeNanos;
       // 說明出現掉幀狀況,注意只有 jitterNanos 大於 16.6 ms 才說明掉幀,不然只是輕微的延遲。
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."); }
          // 當發生掉幀後,須要計算被耽誤的時間。好比處理了 36.6ms, 一個週期是 16.6 ms, 至關於延遲了 3.4 ms 執行
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; if (DEBUG_JANK) { Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + "which is more than the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! " + "Skipping " + skippedFrames + " frames and setting frame " + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); }
          // 修正當前幀的時間 = 開始時間 - 耽誤時間 frameTimeNanos
= startNanos - lastFrameOffset; }        // 當前時間小於前一幀時間,不執行操做 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."); }
          // 直接請求下一個 vsync 信號 scheduleVsyncLocked();
return; }        // 大於 1 說明採用的默認幀數的一半,所以須要根據時間間隔來判斷是否有必要執行繪製 if (mFPSDivisor > 1) { long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
            // 時間間隔小於指定的時間,繼續請求下一個 vsync 信號 scheduleVsyncLocked();
return; } }        // 保存當前幀的相關信息 mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; } try {
       // 執行相關 callbacks 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(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (DEBUG_FRAMES) { final long endNanos = System.nanoTime(); Log.d(TAG, "Frame " + frame + ": Finished, took " + (endNanos - startNanos) * 0.000001f + " ms, latency " + (startNanos - frameTimeNanos) * 0.000001f + " ms."); } } 

總結起來其實主要是兩個操做:

設置當前 frame 的啓動時間

判斷是否跳幀,若跳幀修正當前 frame 的啓動時間到最近的 VSync 信號時間。若是沒跳幀,當前 frame 啓動時間直接設置爲當前 VSync 信號時間。修正完時間後,不管當前 frame 是否跳幀,使得當前 frame 的啓動時間與 VSync 信號仍是在一個節奏上的,可能延後了一到幾個週期,可是都是在下一個 vsync 信號到來才進行處理 。

以下圖所示是時間修正的一個例子,


 
沒有跳幀但延遲
 

因爲第二個 frame 執行超時,第三個 frame 實際啓動時間比第三個 VSync 信號到來時間要晚,由於這時候延時比較小,沒有超過一個時鐘週期,系統仍是將 frameTimeNanos3 傳給回調,回調拿到的時間和 VSync 信號同步。

再來看看下圖:

跳幀

因爲第二個 frame執行時間超過 2 個時鐘週期,致使第三個 frame 延後執行時間大於一個時鐘週期,系統認爲這時候影響較大,斷定爲跳幀了,將第三個 frame 的時間修正爲 frameTimeNanos4,比 VSync 真正到來的時間晚了一個時鐘週期。

時間修正,既保證了doFrame操做和 VSync 保持同步節奏,又保證明際啓動時間與記錄的時間點相差不會太大,便於同步及分析。

順序執行callBack隊列裏面的callback

而後接下來看看 doCallbacks 的執行過程:

    void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            // We use "now" to determine when callbacks become due because it's possible
            // for earlier processing phases in a frame to post callbacks that should run
            // in a following phase, such as an input event that causes an animation to start.
            final long now = System.nanoTime();
       // callbacks
= mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; // Update the frame time if necessary when committing the frame. // We only update the frame time if we are more than 2 frames late reaching // the commit phase. This ensures that the frame time which is observed by the // callbacks will always increase from one frame to the next and never repeat. // We never want the next frame's starting frame time to end up being less than // or equal to the previous frame's commit frame time. Keep in mind that the // next frame has most likely already been scheduled by now so we play it // safe by ensuring the commit time is always at least one frame behind.
       // commit 類型是最後執行的,若是此時發現前面處理時間過長,就會進行糾正。
if (callbackType == Choreographer.CALLBACK_COMMIT) { final long jitterNanos = now - frameTimeNanos; Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos); if (jitterNanos >= 2 * mFrameIntervalNanos) { final long lastFrameOffset = jitterNanos % mFrameIntervalNanos + mFrameIntervalNanos; if (DEBUG_JANK) { Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f) + " ms which is more than twice the frame interval of " + (mFrameIntervalNanos * 0.000001f) + " ms! " + "Setting frame time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); mDebugPrintNextFrameTimeDelta = true; } frameTimeNanos = now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; } } } try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]); for (CallbackRecord c = callbacks; c != null; c = c.next) { if (DEBUG_FRAMES) { Log.d(TAG, "RunCallback: type=" + callbackType + ", action=" + c.action + ", token=" + c.token + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); }
          // 運行每個 callback c.run(frameTimeNanos); } }
finally { synchronized (mLock) { mCallbacksRunning = false; do { final CallbackRecord next = callbacks.next; recycleCallbackLocked(callbacks); callbacks = next; } while (callbacks != null); } Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }

callback的類型有如下 4 種,除了文章一開始提到的 3 種外,還有一個 CALLBACK_COMMIT。

  1. CALLBACK_INPUT:輸入

  2. CALLBACK_ANIMATION:動畫

  3. CALLBACK_TRAVERSAL:遍歷,執行 measure、layout、draw

  4. CALLBACK_COMMIT:遍歷完成的提交操做,用來修正動畫啓動時間

而後看上面的源碼,分析一下每一個 callback 的執行過程:

1. callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( now / TimeUtils.NANOS_PER_MS);

獲得執行時間在當前時間以前的全部 CallBack,保存在單鏈表中。每種類型的 callback 按執行時間前後順序排序分別存在一個單鏈表裏面。爲了保證當前 callback 執行時新 post 進來的 callback 在下一個 frame 時才被執行,這個地方 extractDueCallbacksLocked 會將須要執行的 callback 和之後執行的 callback 斷開變成兩個鏈表,新 post 進來的 callback 會被放到後面一個鏈表中。當前 frame 只會執行前一個鏈表中的 callback,保證了在執行 callback 時,若是callback中Post相同類型的callback,這些新加的 callback 將在下一個 frame 啓動後纔會被執行。

2. 接下來,看一大段註釋,若是類型是 CALLBACK_COMMIT,而且當前 frame 渲染時間超過了兩個時鐘週期,則將當前提交時間修正爲上一個垂直同步信號時間。爲了保證下一個frame 的提交時間和當前 frame 時間相差爲一且不重複。
這個地方註釋挺難看懂,實際上這個地方 CALLBACK_COMMIT 是爲了解決 ValueAnimator 的一個問題而引入的,主要是解決由於遍歷時間過長致使動畫時間啓動過長,時間縮短,致使跳幀,這裏修正動畫第一個 frame 開始時間延後來改善,這時候才表示動畫真正啓動。爲何不直接設置當前時間而是回溯一個時鐘週期以前的時間呢?看註釋,這裏若是設置爲當前 frame 時間,由於動畫的第一個 frame 其實已經繪製完成,第二個 frame 這時候已經開始了,設置爲當前時間會致使這兩個 frame 時間同樣,致使衝突。

詳細狀況請看官方針對這個問題的修改。Fix animation start jank due to expensive layout operations.

以下圖所示:

 
修正commit時間

好比說在第二個frame開始執行時,開始渲染動畫的第一個畫面,第二個frame執行時間超過了兩個時鐘週期,Draw操做執行結束後,這時候完成了動畫第一幀的渲染,動畫實際上還沒開始,可是時間已通過了兩個時鐘週期,後面動畫實際執行時間將會縮短一個時鐘週期。這時候系統經過修正commit時間到frameTimeNanos的上一個VSync信號時間,即完成動畫第一幀渲染以前的VSync信號到來時間,修正了動畫啓動時間,保證動畫執行時間的正確性。

調用 c.run(frameTimeNanos) 執行回調

    private static final class CallbackRecord {
        public CallbackRecord next;
        public long dueTime;
        public Object action; // Runnable or FrameCallback
        public Object token;

        @UnsupportedAppUsage
        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();
            }
        }
    }

CallbackRecord 的代碼如上所示。

發起繪製的請求

doFrame 的邏輯瞭解清楚了,可是關於發起 vsync 請求的邏輯卻沒有講。

// ViewRootImpl    
 @UnsupportedAppUsage
    void scheduleTraversals() {
     // 若是已經請求繪製了,就不會再次請求,由於屢次請求,只有有一個執行就知足要求了
if (!mTraversalScheduled) { mTraversalScheduled = true;
       // 同步 mTraversalBarrier
= mHandler.getLooper().getQueue().postSyncBarrier();
       // 發送一個 callback 用於在下一幀來臨時候處理 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable,
null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); }
       // 通知稍候繪製 notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }

 接着會調用 postCallbackDelayed:

    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }

        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }

主要是作一些邏輯判斷,確保傳入的是對的。 

接着又會調用 postCallbackDelayedInternal,保存 callback,併發起請求下一幀。

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        // 小於當前時間,說明須要當即執行 if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
          // 發送一個延遲 msg Message msg
= mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } }

簡單來講,就是判斷當前是否有必要發起一個繪製請求,好比你發了一個 500ms 後重繪的消息,對於這個消息,會在 500ms 後在進行處理。但若是不是延遲消息,那說明須要當即處理。可是對於 view 的繪製邏輯,必須得等到下一個 vsync 到來的時候纔會真正進行繪製。

接下來看看 scheduleFrameLocked 的邏輯:

   private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
         // 若是是在一個 looper 線程中,那麼直接執行請求就好 if (isRunningOnLooperThreadLocked()) { scheduleVsyncLocked(); } else {
            // 若是是在主線程,那麼須要發送一個請求 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."); } Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); } } }

這裏主要是根據具體狀況來判斷如何發起下一幀的繪製。對於採用 vsync 信號的主線程,會發送一個 MSG_DO_SCHEDULE_VSYNC 的消息,插到最前面,確保能夠最先執行。

當收到 MSG_DO_SCHEDULE_VSYNC 消息後,就會給他安排請求 vsync 信號的請求,最後會會調到 onVsync 方法。

    void doScheduleVsync() {
        synchronized (mLock) {
            if (mFrameScheduled) {
                scheduleVsyncLocked();
            }
        }
    }


    @UnsupportedAppUsage
    private void scheduleVsyncLocked() {
       // 發起獲取 vsync 信號的請求
        mDisplayEventReceiver.scheduleVsync();
    }   

    /**
     * Schedules a single vertical sync pulse to be delivered when the next
     * display frame begins.
     */
    @UnsupportedAppUsage
    public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
            nativeScheduleVsync(mReceiverPtr);
        }
    } 
到這裏,就把 Choreographer 的基本原理都講完了。
 

源碼小結

  • Choreographer 是線程單例的,並且必需要和一個 Looper 綁定,由於其內部有一個 Handler 須要和 Looper 綁定,通常是 App 主線程的 Looper 綁定

  • DisplayEventReceiver 是一個 abstract class,其 JNI 的代碼部分會建立一個IDisplayEventConnection 的 Vsync 監聽者對象。這樣,來自 AppEventThread 的 VSYNC 中斷信號就能夠傳遞給 Choreographer 對象了。當 Vsync 信號到來時,DisplayEventReceiver 的 onVsync 函數將被調用。

  • DisplayEventReceiver 還有一個 scheduleVsync 函數。當應用須要繪製UI時,將首先申請一次 Vsync 中斷,而後再在中斷處理的 onVsync 函數去進行繪製。

  • Choreographer 定義了一個 FrameCallbackinterface,每當 Vsync 到來時,其 doFrame 函數將被調用。這個接口對 Android Animation 的實現起了很大的幫助做用。之前都是本身控制時間,如今終於有了固定的時間中斷。

  • Choreographer 的主要功能是,當收到 Vsync 信號時,去調用使用者經過 postCallback 設置的回調函數。目前一共定義了五種類型的回調,它們分別是:ListView 的 Item 初始化(obtain\setup) 會在 input 裏面也會在 animation 裏面,這取決於

    • CALLBACK_INPUT : 處理輸入事件處理有關

    • CALLBACK_ANIMATION : 處理 Animation 的處理有關

    • CALLBACK_INSETS_ANIMATION : 處理 Insets Animation 的相關回調

    • CALLBACK_TRAVERSAL : 處理和 UI 等控件繪製有關

    • CALLBACK_COMMIT : 處理 Commit 相關回調

  • CALLBACK_INPUT 、CALLBACK_ANIMATION 會修改 view 的屬性,因此要比 CALLBACK_TRAVERSAL 先執行

 
最後附上一張時序圖,來回顧一下整個流程
在這裏插入圖片描述
 
另外,知道源碼後,也就知道如何去發現 app 卡頓狀況了。 詳見  Android 教你如何發現 APP 卡頓

 

參考文章

Android 基於 Choreographer 的渲染機制詳解

相關文章
相關標籤/搜索