Choreographer工做邏輯總結

爲了更好的理解使用Choreographer監控App FPS的原理,本文先來梳理一下Choreographer的工做原理。git

Choreographer主要是用來協調動畫、輸入和繪製事件運行的。它經過接收Vsync信號來調度應用下一幀渲染時的動做。github

對 Vsync 信號的監聽

Choreographer會經過Choreographer.FrameDisplayEventReceiver來監聽底層HWC觸發的Vsync信號:bash

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {

    }
}
複製代碼

Vsync信號能夠理解爲底層硬件的一個系統中斷,它每16ms會產生一次。上面onVsync()的每一個參數的意義爲:ide

timestampNanos : Vsync信號到來的時間, 這個時間使用的是底層JVM nanoscends -> System.nanoTimeoop

builtInDisplayId : 此時SurfaceFlinger內置的display idpost

frame : 幀號,隨着onVsync的回調數增長動畫

onVsync的回調邏輯

onVsync何時會調用呢?ui

Choreographer.scheduleVsyncLocked()會請求下一次Vsync信號到來時回調FrameDisplayEventReceiver.onVsync()方法:spa

private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();
}
複製代碼

Vsync信號到來時執行callback

設置callback

Choregrapher提供了下面方法設置callback:線程

public void postCallback(int callbackType, Runnable action, Object token) 
public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis)
public void postFrameCallback(FrameCallback callback)
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) 
複製代碼

Choregrapher中存在多個Callback Queue, 常見的Callback Queue的類型有:

Choreographer.CALLBACK_INPUT       輸入事件,好比鍵盤
Choreographer.CALLBACK_ANIMATION   動畫
Choreographer.CALLBACK_TRAVERSAL   好比`ViewRootImpl.scheduleTraversals, layout or draw`
Choreographer.CALLBACK_COMMIT           
複製代碼

上面4個事件會在一次Vsync信號到來時依次執行。

callback的執行邏輯

ViewRootImpl.scheduleTraversals爲例:

void scheduleTraversals() {
    ...
    mChoreographer.postCallback(
        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
複製代碼

即給Choregrapher提交了一個Choreographer.CALLBACK_TRAVERSAL類型的callback去執行。

postCallback()裏面的具體執行邏輯就不分析了,這裏直接說一下關鍵邏輯:

  1. 切換到 Choregrapher建立時所在的線程去調用scheduleFrameLocked()方法,設置mFrameScheduled = true
  2. 調用scheduleVsyncLocked請求下一次Vsync信號回調
  3. FrameDisplayEventReceiver.onVsync()會生成一個消息,而後發送到Choreographer.mHander的消息隊列
  4. Choreographer.mHander取出上面onVsync中發送的消息,執行Choreographer.doFrame()方法,doFrame()中判斷mFrameScheduled是否爲true,若是爲true的話就上面四種callback

綜上所述Choreographer的工做原理以下圖:

doFrame() 的時間參數

咱們來看一下這個方法(主要關注一下時間參數):

void doFrame(long frameTimeNanos, int frame) {

    final long startNanos;
    if (!mFrameScheduled) {
        return; // no work to do
    }

    long intendedFrameTimeNanos = frameTimeNanos; 
    startNanos = System.nanoTime();

    final long jitterNanos = startNanos - frameTimeNanos;

    if (jitterNanos >= mFrameIntervalNanos) {  //16ms
        final long skippedFrames = jitterNanos / mFrameIntervalNanos;
        final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
        frameTimeNanos = startNanos - lastFrameOffset;      
    }

    mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
    mFrameScheduled = false;
    mLastFrameTimeNanos = frameTimeNanos;

    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
複製代碼

解釋一下上面一些時間相關參數的含義:

intendedFrameTimeNanos: 預計這一幀開始渲染的時間

frameTimeNanos: 這一幀真正開始渲染的時間。在 startNanos - frameTimeNanos < mFrameIntervalNanos,其實就等於intendedFrameTimeNanos

jitterNanos: 真正渲染時間點和預計渲染時間點之差

mFrameIntervalNanos: 每一幀指望渲染的時間, 固定爲16ms

skippedFrames : jitterNanos總共跳過了多少幀。

mLastFrameTimeNanos : 上一次渲染一幀的時間點

jitterNanos > mFrameIntervalNanos 在何時會成立呢?

其實就是咱們常說的丟幀: 好比咱們連續提交了兩個Choreographer.CALLBACK_TRAVERSAL callback。若是一個callback的執行時間大於16ms,那麼就會形成:

startNanos - frameTimeNanos > mFrameIntervalNanos(16ms)
複製代碼

doCallback(int callbackType, long frameTimeNanos)

這個方法的邏輯並不複雜 : 獲取callbackType對應的Callback Queue, 取出這個隊列中已通過期的calllback進行執行。

void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
    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));
        }
        c.run(frameTimeNanos);
    }
}
複製代碼

Choreographer與主線程消息循環的關係

上面咱們已經知道onVsync把要執行doFrame的消息放入了Choreographer.mHander的消息隊列。

這裏Choreographer.mHander的消息隊列其實就是主線程的消息,因此doFrame方法實際上是由主線程的消息循環來調度的

咱們看一下Choreographer實例化時的Looper:

private static final ThreadLocal<Choreographer> sThreadInstance =
        new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
        Looper looper = Looper.myLooper();
        ...
        Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
        if (looper == Looper.getMainLooper()) {
            mMainInstance = choreographer;
        }
        return choreographer;
    }
};
複製代碼

即取的是當前線程的Looper,因此donFrame()是在主線程的消息循環中調度的。

參考文章:

Android Choreographer 源碼

Choreographer原理

後面會分析Tencent/matrix的實現原理,歡迎關注個人Android進階計劃看更多文章。