Android 基於 Choreographer 的渲染機制詳解

主線程運行機制的本質

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

MethodTrace 圖示android

圖片

Systrace 圖示git

圖片

演進

引入 Vsync 以前的 Android 版本,渲染一幀相關的 Message ,中間是沒有間隔的,上一幀繪製完,下一幀的 Message 緊接着就開始被處理。這樣的問題就是,幀率不穩定,可能高也可能低,不穩定,以下圖github

MethodTrace 圖示shell

圖片

Systrace 圖示app

圖片

對於用戶來講,穩定的幀率纔是好的體驗,好比你玩王者榮耀,相比 fps 在 60 和 40 之間頻繁變化,用戶感受更好的是穩定在 50 fps 的狀況。異步

因此 Android 的演進中,引入了 Vsync + TripleBuffer + Choreographer 的機制,其主要目的就是提供一個穩定的幀率輸出機制,讓軟件層和硬件層能夠以共同的頻率一塊兒工做。ide

引入 Choreographer

Choreographer 的引入,主要是配合 Vsync ,給上層 App 的渲染提供一個穩定的 Message 處理的時機,也就是 Vsync 到來的時候 ,系統經過對 Vsync 信號週期的調整,來控制每一幀繪製操做的時機. 目前大部分手機都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系統爲了配合屏幕的刷新頻率,將 Vsync 的週期也設置爲 16.6 ms,每一個 16.6 ms , Vsync 信號喚醒 Choreographer 來作 App 的繪製操做 ,這就是引入 Choreographer 的主要做用函數

Choreographer 簡介

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

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

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

瞭解 Choreographer 還能夠幫助 App 開發者知道程序每一幀運行的基本原理,也能夠加深對 Message、Handler、Looper、MessageQueue、Measure、Layout、Draw 的理解 , 不少 APM 工具也用到了 Choreographer( 利用 FrameCallback + FrameInfo ) + MessageQueue ( 利用 IdleHandler ) + Looper ( 設置自定義 MessageLogging) 這些組合拳,深刻了解了這些以後,再去作優化,腦子裏的思路會更清晰。

另外雖然畫圖是一個比較好的解釋流程的好路子,可是我我的不是很喜歡畫圖,由於平時 Systrace 和 MethodTrace 用的比較多,Systrace 是按從左到右展現整個系統的運行狀況的一個工具(包括 cpu、SurfaceFlinger、SystemServer、App 等關鍵進程),使用 SystraceMethodTrace 也能夠很方便地展現關鍵流程。當你對系統代碼比較熟悉的時候,看 Systrace 就能夠和手機運行的實際狀況對應起來。因此下面的文章除了一些網圖以外,其餘的我會多以 Systrace 來展現.

從 Systrace 的角度來看 Choreogrepher 的工做流程

下圖以滑動桌面爲例子,咱們先看一下從左到右滑動桌面的一個完整的預覽圖(App 進程),能夠看到 Systrace 中從左到右,每個綠色的幀都表示一幀,表示最終咱們能夠手機上看到的畫面

  1. 圖中每個灰色的條和白色的條寬度是一個 Vsync 的時間,也就是 16.6ms
  2. 每一幀處理的流程:接收到 Vsync 信號回調-> UI Thread --> RenderThread --> SurfaceFlinger(圖中未顯示)
  3. UI Thread 和 RenderThread 就能夠完成 App 一幀的渲染,渲染完的 Buffer 拋給 SurfaceFlinger 去合成,而後咱們就能夠在屏幕上看到這一幀了
  4. 能夠看到桌面滑動的每一幀耗時都很短(Ui Thread 耗時 + RenderThread 耗時),可是因爲 Vsync 的存在,每一幀都會等到 Vsync 纔會去作處理

圖片

有了上面這個總體的概念,咱們將 UI Thread 的每一幀放大來看,看看 Choreogrepher 的位置以及 Choreogrepher 是怎麼組織每一幀的

圖片

Choreographer 的工做流程

  1. Choreographer 初始化
  2. 初始化 FrameHandler ,綁定 Looper
  3. 初始化 FrameDisplayEventReceiver ,與 SurfaceFlinger 創建通訊用於接收和請求 Vsync
  4. 初始化 CallBackQueues
  5. SurfaceFlinger 的 appEventThread 喚醒發送 Vsync ,Choreographer 回調 FrameDisplayEventReceiver.onVsync , 進入 SurfaceFlinger 的主處理函數 doFrame
  6. Choreographer.doFrame 計算掉幀邏輯
  7. Choreographer.doFrame 處理 Choreographer 的第一個 callback : input
  8. Choreographer.doFrame 處理 Choreographer 的第二個 callback : animation
  9. Choreographer.doFrame 處理 Choreographer 的第三個 callback : insets animation
  10. Choreographer.doFrame 處理 Choreographer 的第四個 callback : traversal
  11. traversal-draw 中 UIThread 與 RenderThread 同步數據
  12. Choreographer.doFrame 處理 Choreographer 的第五個 callback : commit ?
  13. RenderThread 處理繪製數據,真正進行渲染
  14. 將渲染好的 Buffer swap 給 SurfaceFlinger 進行合成

第一步初始化完成後,後續就會在步驟 2-9 之間循環

同時也附上這一幀所對應的 MethodTrace(這裏預覽一下便可,下面會有詳細的大圖)

圖片

下面咱們就從源碼的角度,來看一下具體的實現

源碼解析

Choreographer 的初始化

Choreographer 的單例初始化

// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
        new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
        // 獲取當前線程的 Looper
        Looper looper = Looper.myLooper();
        ......
        // 構造 Choreographer 對象
        Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
        if (looper == Looper.getMainLooper()) {
            mMainInstance = choreographer;
        }
        return choreographer;
    }
};
複製代碼

Choreographer 的構造函數

private Choreographer(Looper looper, int vsyncSource) {
    mLooper = looper;
    // 1. 初始化 FrameHandler
    mHandler = new FrameHandler(looper);
    // 2. 初始化 DisplayEventReceiver
    mDisplayEventReceiver = USE_VSYNC
            ? new FrameDisplayEventReceiver(looper, vsyncSource)
            : null;
    mLastFrameTimeNanos = Long.MIN_VALUE;
    mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
    //3. 初始化 CallbacksQueues
    mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
        mCallbackQueues[i] = new CallbackQueue();
    }
    ......
}
複製代碼

FrameHandler

private final class FrameHandler extends Handler {
    ......
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DO_FRAME://開始渲染下一幀的操做
                doFrame(System.nanoTime(), 0);
                break;
            case MSG_DO_SCHEDULE_VSYNC://請求 Vsync 
                doScheduleVsync();
                break;
            case MSG_DO_SCHEDULE_CALLBACK://處理 Callback
                doScheduleCallback(msg.arg1);
                break;
        }
    }
}
複製代碼

Choreographer 初始化鏈

在 Activity 啓動過程,執行完 onResume 後,會調用 Activity.makeVisible(),而後再調用到 addView(), 層層調用會進入以下方法

ActivityThread.handleResumeActivity(IBinder, boolean, boolean, String) (android.app) 
-->WindowManagerImpl.addView(View, LayoutParams) (android.view) 
  -->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view) 
    -->ViewRootImpl.ViewRootImpl(Context, Display) (android.view) 
    public ViewRootImpl(Context context, Display display) {
        ......
        mChoreographer = Choreographer.getInstance();
        ......
    }
複製代碼

FrameDisplayEventReceiver 簡介

Vsync 的註冊和回調經過 FrameDisplayEventReceiver 這個類,因此能夠先簡單介紹一下。 FrameDisplayEventReceiver 繼承 DisplayEventReceiver , 有三個比較重要的方法

  1. onVsync -- Vsync 信號回調
  2. run -- 執行 doFrame
  3. scheduleVsync -- 請求 Vsync 信號
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
    ......
    @Override
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
        ......
        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);
    }
    
    public void scheduleVsync() {
        ......  
        nativeScheduleVsync(mReceiverPtr);
        ......
    }
}
複製代碼

Choreographer 中 Vsync 的註冊

從下面的函數調用棧能夠看到,Choreographer 的內部類 FrameDisplayEventReceiver.onVsync 負責接收 Vsync 回調,通知 UIThread 進行數據處理。

那麼 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 後續的代碼能夠本身跟一下,能夠對照這篇文章和源碼,因爲篇幅比較多,這裏就不細寫了(www.jianshu.com/p/304f56f5d…) , 後續梳理好這一塊的邏輯後,會在另外的文章更新。

DisplayEventReceiver 初始化完成後,Choreographer 與 SurfaceFlinger 的 Vsync 信號傳遞通道就創建好了,後續有 Vsync 信號到來時,就能夠沿着這條通道,一路回調到 Java 層的 Choreographer.FrameDisplayEventReceiver.onVsync 方法了。

Choreographer 處理一幀的邏輯

Choreographer 處理繪製的邏輯核心在 Choreographer.doFrame 函數中,從下圖能夠看到,FrameDisplayEventReceiver.onVsync post 了本身,其 run 方法直接調用了 doFrame 開始一幀的邏輯處理

圖片

android/view/Choreographer.java

public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
    ......
    mTimestampNanos = timestampNanos;
    mFrame = frame;
    Message msg = Message.obtain(mHandler, this);
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
public void run() {
    mHavePendingVsync = false;
    doFrame(mTimestampNanos, mFrame);
}
複製代碼

doFrame 函數主要作下面幾件事

  1. 計算掉幀邏輯
  2. 記錄幀繪製信息
  3. 執行 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_INSETS_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT

計算掉幀邏輯

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        ......
        long intendedFrameTimeNanos = frameTimeNanos;
        startNanos = System.nanoTime();
        final long jitterNanos = startNanos - frameTimeNanos;
        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.");
            }
        }
        ......
    }
    ......
}
複製代碼

Choreographer.doFrame 的掉幀檢測比較簡單,從下圖能夠看到,Vsync 信號到來的時候會標記一個 start_time ,執行 doFrame 的時候標記一個 end_time ,這兩個時間差就是 Vsync 處理時延,也就是掉幀

圖片

咱們以 Systrace 的掉幀的實際狀況來看掉幀的計算邏輯

圖片

這裏須要注意的是,這種方法計算的掉幀,是前一幀的掉幀狀況,而不是這一幀的掉幀狀況,這個計算方法是有缺陷的,會致使有的掉幀沒有被計算到

記錄幀繪製信息

Choreographer 中 FrameInfo 來負責記錄幀的繪製信息,doFrame 執行的時候,會把每個關鍵節點的繪製時間記錄下來,咱們使用 dumpsys gfxinfo 就能夠看到。固然 Choreographer 只是記錄了一部分,剩餘的部分在 hwui 那邊來記錄。

從 FrameInfo 這些標誌就能夠看出記錄的內容,後面咱們看 dumpsys gfxinfo 的時候數據就是按照這個來排列的

// Various flags set to provide extra metadata about the current frame
private static final int FLAGS = 0;

// Is this the first-draw following a window layout?
public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;

// A renderer associated with just a Surface, not with a ViewRootImpl instance.
public static final long FLAG_SURFACE_CANVAS = 1 << 2;

@LongDef(flag = true, value = {
        FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS })
@Retention(RetentionPolicy.SOURCE)
public @interface FrameInfoFlags {}

// The intended vsync time, unadjusted by jitter
private static final int INTENDED_VSYNC = 1;

// Jitter-adjusted vsync time, this is what was used as input into the
// animation & drawing system
private static final int VSYNC = 2;

// The time of the oldest input event
private static final int OLDEST_INPUT_EVENT = 3;

// The time of the newest input event
private static final int NEWEST_INPUT_EVENT = 4;

// When input event handling started
private static final int HANDLE_INPUT_START = 5;

// When animation evaluations started
private static final int ANIMATION_START = 6;

// When ViewRootImpl#performTraversals() started
private static final int PERFORM_TRAVERSALS_START = 7;

// When View:draw() started
private static final int DRAW_START = 8;
複製代碼

doFrame 函數記錄從 Vsync time 到 markPerformTraversalsStart 的時間

void doFrame(long frameTimeNanos, int frame) {
    ......
    mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
    // 處理 CALLBACK_INPUT Callbacks 
    mFrameInfo.markInputHandlingStart();
    // 處理 CALLBACK_ANIMATION Callbacks
    mFrameInfo.markAnimationsStart();
    // 處理 CALLBACK_INSETS_ANIMATION Callbacks
    // 處理 CALLBACK_TRAVERSAL Callbacks
    mFrameInfo.markPerformTraversalsStart();
    // 處理 CALLBACK_COMMIT Callbacks
    ......
}
複製代碼

執行 Callbacks

void doFrame(long frameTimeNanos, int frame) {
    ......
    // 處理 CALLBACK_INPUT Callbacks 
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    // 處理 CALLBACK_ANIMATION Callbacks
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    // 處理 CALLBACK_INSETS_ANIMATION Callbacks
    doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
    // 處理 CALLBACK_TRAVERSAL Callbacks
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    // 處理 CALLBACK_COMMIT Callbacks
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    ......
}
複製代碼

Input 回調調用棧

**input callback 通常是執行 **ViewRootImpl.ConsumeBatchedInputRunnable

android/view/ViewRootImpl.java

final class ConsumeBatchedInputRunnable implements Runnable {
    @Override
    public void run() {
        doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
    }
}
void doConsumeBatchedInput(long frameTimeNanos) {
    if (mConsumeBatchedInputScheduled) {
        mConsumeBatchedInputScheduled = false;
        if (mInputEventReceiver != null) {
            if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
                    && frameTimeNanos != -1) {
                scheduleConsumeBatchedInput();
            }
        }
        doProcessInputEvents();
    }
}
複製代碼

Input 時間通過處理,最終會傳給 DecorView 的 dispatchTouchEvent,這就到了咱們熟悉的 Input 事件分發

圖片

Animation 回調調用棧

通常咱們接觸的多的是調用 View.postOnAnimation 的時候,會使用到 CALLBACK_ANIMATION

public void postOnAnimation(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.mChoreographer.postCallback(
                Choreographer.CALLBACK_ANIMATION, action, null);
    } else {
        // Postpone the runnable until we know
        // on which thread it needs to run.
        getRunQueue().post(action);
    }
}
複製代碼

那麼通常是何時回調用到 View.postOnAnimation 呢,我截取了一張圖,你們能夠本身去看一下,接觸最多的應該是 startScroll,Fling 這種操做

圖片

其調用棧根據其 post 的內容,下面是桌面滑動鬆手以後的 fling 動畫。

圖片

另外咱們的 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    if (callback == null) {
        throw new IllegalArgumentException("callback must not be null");
    }

    postCallbackDelayedInternal(CALLBACK_ANIMATION,
            callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
複製代碼

Traversal 調用棧

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //爲了提升優先級,先 postSyncBarrier
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        // 真正開始執行 measure、layout、draw
        doTraversal();
    }
}
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 這裏把 SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        // 真正開始
        performTraversals();
    }
}
private void performTraversals() {
      // measure 操做
      if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
      }
      // layout 操做
      if (didLayout) {
          performLayout(lp, mWidth, mHeight);
      }
      // draw 操做
      if (!cancelDraw && !newSurface) {
          performDraw();
      }
}
複製代碼

doTraversal 的 TraceView 示例

圖片

下一幀的 Vsync 請求

因爲動畫、滑動、Fling 這些操做的存在,咱們須要一個連續的、穩定的幀率輸出機制。這就涉及到了 Vsync 的請求邏輯,在連續的操做,好比動畫、滑動、Fling 這些狀況下,每一幀的 doFrame 的時候,都會根據狀況觸發下一個 Vsync 的申請,這樣咱們就能夠得到連續的 Vsync 信號。咱們下面以 Animation 爲例,看看 Animation 是如何驅動下一個 Vsync ,來持續更新畫面的

ObjectAnimator 動畫驅動邏輯

android/animation/ObjectAnimator.java

public void start() {
    super.start();
}
複製代碼

android/animation/ValueAnimator.java

private void start(boolean playBackwards) {
    ......
    addAnimationCallback(0); // 動畫 start 的時候添加 Animation Callback 
    ......
}
private void addAnimationCallback(long delay) {
    ......
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}
複製代碼

android/animation/AnimationHandler.java

public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (mAnimationCallbacks.size() == 0) {
        // post FrameCallback
        getProvider().postFrameCallback(mFrameCallback);
    }
    ......
}

// 這裏的 mFrameCallback 回調 doFrame,裏面 post了本身
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());
        if (mAnimationCallbacks.size() > 0) {
            // post 本身
            getProvider().postFrameCallback(this);
        }
    }
};
複製代碼

調用 postFrameCallback 會走到 mChoreographer.postFrameCallback ,這裏就會觸發 Choreographer 的 Vsync 請求邏輯

android/animation/AnimationHandler.java

public void postFrameCallback(Choreographer.FrameCallback callback) {
    mChoreographer.postFrameCallback(callback);
}
複製代碼

android/view/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 ->scheduleVsyncLocked-> mDisplayEventReceiver.scheduleVsync ->nativeScheduleVsync
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}
複製代碼

經過上面的 Animation.start 設置,利用了 Choreographer.FrameCallback 接口,每一幀都去請求下一個 Vsync 動畫過程當中一幀的 TraceView 示例

圖片

源碼小結

  1. Choreographer 是線程單例的,並且必需要和一個 Looper 綁定,由於其內部有一個 Handler 須要和 Looper 綁定,通常是 App 主線程的 Looper 綁定
  2. **DisplayEventReceiver **是一個 abstract class,其 JNI 的代碼部分會建立一個IDisplayEventConnection 的 Vsync 監聽者對象。這樣,來自 AppEventThread 的 VSYNC 中斷信號就能夠傳遞給 Choreographer 對象了。當 Vsync 信號到來時,DisplayEventReceiver 的 onVsync 函數將被調用。
  3. **DisplayEventReceiver **還有一個 scheduleVsync 函數。當應用須要繪製UI時,將首先申請一次 Vsync 中斷,而後再在中斷處理的 onVsync 函數去進行繪製。
  4. Choreographer 定義了一個 FrameCallback interface,每當 Vsync 到來時,其 doFrame 函數將被調用。這個接口對 Android Animation 的實現起了很大的幫助做用。之前都是本身控制時間,如今終於有了固定的時間中斷。
  5. Choreographer 的主要功能是,當收到 Vsync 信號時,去調用使用者經過 postCallback 設置的回調函數。目前一共定義了五種類型的回調,它們分別是:
  6. CALLBACK_INPUT : 處理輸入事件處理有關
  7. CALLBACK_ANIMATION : 處理 Animation 的處理有關
  8. CALLBACK_INSETS_ANIMATION : 處理 Insets Animation 的相關回調
  9. CALLBACK_TRAVERSAL : 處理和 UI 等控件繪製有關
  10. CALLBACK_COMMIT : 處理 Commit 相關回調
  11. ListView 的 Item 初始化(obtain\setup) 會在 input 裏面也會在 animation 裏面,這取決於
  12. CALLBACK_INPUTCALLBACK_ANIMATION 會修改 view 的屬性,因此要先與 CALLBACK_TRAVERSAL 執行

APM 與 Choreographer

因爲 Choreographer 的位置,許多性能監控的手段都是利用 Choreographer 來作的,除了自帶的掉幀計算,Choreographer 提供的 FrameCallback 和 FrameInfo 都給 App 暴露了接口,讓 App 開發者能夠經過這些方法監控自身 App 的性能,其中經常使用的方法以下:

  1. 利用 FrameCallback 的 doFrame 回調
  2. 利用 FrameInfo 進行監控
  3. 使用 :adb shell dumpsys gfxinfo framestats
  4. 示例 :adb shell dumpsys gfxinfo com.meizu.flyme.launcher framestats
  5. 利用 SurfaceFlinger 進行監控
  6. 使用 :adb shell dumpsys SurfaceFlinger --latency
  7. 示例 :adb shell dumpsys SurfaceFlinger --latency com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher#0
  8. 利用 SurfaceFlinger PageFlip 機制進行監控
  9. 使用 : adb service call SurfaceFlinger 1013
  10. 備註:須要系統權限
  11. Choreographer 自身的掉幀計算邏輯
  12. BlockCanary 基於 Looper 的性能監控

利用 FrameCallback 的 doFrame 回調

FrameCallback 接口

public interface FrameCallback {
    public void doFrame(long frameTimeNanos);
}
複製代碼

接口使用

Choreographer.getInstance().postFrameCallback(youOwnFrameCallback );
複製代碼

接口處理

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    ......
    postCallbackDelayedInternal(CALLBACK_ANIMATION,
            callback, FRAME_CALLBACK_TOKEN, delayMillis);
}  
複製代碼

TinyDancer 就是使用了這個方法來計算 FPS (github.com/friendlyrob…)

利用 FrameInfo 進行監控

adb shell dumpsys gfxinfo framestats

Window: StatusBar
Stats since: 17990256398ns
Total frames rendered: 1562
Janky frames: 361 (23.11%)
50th percentile: 6ms
90th percentile: 23ms
95th percentile: 36ms
99th percentile: 101ms
Number Missed Vsync: 33
Number High input latency: 683
Number Slow UI thread: 273
Number Slow bitmap uploads: 8
Number Slow issue draw commands: 18
Number Frame deadline missed: 287
HISTOGRAM: 5ms=670 6ms=128 7ms=84 8ms=63 9ms=38 10ms=23 11ms=21 12ms=20 13ms=25 14ms=39 15ms=65 16ms=36 17ms=51 18ms=37 19ms=41 20ms=20 21ms=19 22ms=18 23ms=15 24ms=14 25ms=8 26ms=4 27ms=6 28ms=3 29ms=4 30ms=2 31ms=2 32ms=6 34ms=12 36ms=10 38ms=9 40ms=3 42ms=4 44ms=5 46ms=8 48ms=6 53ms=6 57ms=4 61ms=1 65ms=0 69ms=2 73ms=2 77ms=3 81ms=4 85ms=1 89ms=2 93ms=0 97ms=2 101ms=1 105ms=1 109ms=1 113ms=1 117ms=1 121ms=2 125ms=1 129ms=0 133ms=1 150ms=2 200ms=3 250ms=0 300ms=1 350ms=1 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0 

---PROFILEDATA---
Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,
0,10158314881426,10158314881426,9223372036854775807,0,10158315693363,10158315760759,10158315769821,10158316032165,10158316627842,10158316838988,10158318055915,10158320387269,10158321770654,428000,773000,
0,10158332036261,10158332036261,9223372036854775807,0,10158332799196,10158332868519,10158332877269,10158333137738,10158333780654,10158333993206,10158335078467,10158337689561,10158339307061,474000,885000,
0,10158348665353,10158348665353,9223372036854775807,0,10158349710238,10158349773102,10158349780863,10158350405863,10158351135967,10158351360446,10158352300863,10158354305654,10158355814509,471000,836000,
0,10158365296729,10158365296729,9223372036854775807,0,10158365782373,10158365821019,10158365825238,10158365975290,10158366547946,10158366687217,10158367240706,10158368429248,10158369291852,269000,476000,
複製代碼

利用 SurfaceFlinger 進行監控

命令解釋:

  1. 數據的單位是納秒,時間是以開機時間爲起始點
  2. 每一次的命令都會獲得128行的幀相關的數據

數據:

  1. 第一行數據,表示刷新的時間間隔refresh_period
  2. 第1列:這一部分的數據表示應用程序繪製圖像的時間點
  3. 第2列:在SF(軟件)將幀提交給H/W(硬件)繪製以前的垂直同步時間,也就是每幀繪製完提交到硬件的時間戳,該列就是垂直同步的時間戳
  4. 第3列:在SF將幀提交給H/W的時間點,算是H/W接受完SF發來數據的時間點,繪製完成的時間點。

掉幀 jank 計算

每一行均可以經過下面的公式獲得一個值,該值是一個標準,咱們稱爲jankflag,若是當前行的jankflag與上一行的jankflag發生改變,那麼就叫掉幀

ceil((C - A) / refresh-period)

利用 SurfaceFlinger PageFlip 機制進行監控

Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
                data.writeInterfaceToken("android.ui.ISurfaceComposer");
mFlinger.transact(1013, data, reply, 0);
final int pageFlipCount = reply.readInt();

final long now = System.nanoTime();
final int frames = pageFlipCount - mLastPageFlipCount;
final long duration = now - mLastUpdateTime;
mFps = (float) (frames * 1e9 / duration);
mLastPageFlipCount = pageFlipCount;
mLastUpdateTime = now;
reply.recycle();
data.recycle();
複製代碼

Choreographer 自身的掉幀計算邏輯

SKIPPED_FRAME_WARNING_LIMIT 默認爲30 , 應用能夠獲取 hook 這個值進行修改

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.");
    }
}
複製代碼

App 能夠經過 hook 修改這個默認值,以便根據這個來監控性能

Field field = Choreographer.class.getDeclaredField("SKIPPED_FRAME_WARNING_LIMIT");
field.setAccessible(true);
field.set(Choreographer.class, 0);
複製代碼

BlockCanary

Blockcanary 計算作性能監控使用的是 Looper 的消息機制,經過對 MessageQueue 中每個 Message 的先後進行記錄,打到監控性能的目的

android/os/Looper.java

public static void loop() {
    ...
    for (;;) {
        ...
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg);
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        ...
    }
}
複製代碼

MessageQueue 與 Choreographer

所謂的異步消息其實就是這樣的,咱們能夠經過 enqueueBarrier 往消息隊列中插入一個 Barrier,那麼隊列中執行時間在這個 Barrier 之後的同步消息都會被這個 Barrier 攔截住沒法執行,直到咱們調用 removeBarrier 移除了這個 Barrier,而異步消息則沒有影響,消息默認就是同步消息,除非咱們調用了 Message 的 setAsynchronous,這個方法是隱藏的。只有在初始化 Handler 時經過參數指定往這個 Handler 發送的消息都是異步的,這樣在 Handler 的 enqueueMessage 中就會調用 Message 的 setAsynchronous 設置消息是異步的,從上面 Handler.enqueueMessage 的代碼中能夠看到。

所謂異步消息,其實只有一個做用,就是在設置 Barrier 時仍能夠不受 Barrier 的影響被正常處理,若是沒有設置 Barrier,異步消息就與同步消息沒有區別,能夠經過 removeSyncBarrier 移除 Barrier

SyncBarrier 在 Choreographer 中使用的一個示例

scheduleTraversals 的時候 postSyncBarrier

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //爲了提升優先級,先 postSyncBarrier
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}
複製代碼

doTraversal 的時候 removeSyncBarrier

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 這裏把 SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        // 真正開始
        performTraversals();
    }
}
複製代碼

Choreographer post Message 的時候,會把這些消息設爲 Asynchronous ,這樣Choreographer 中的這些 Message 的優先級就會比較高,

Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
複製代碼

廠商優化

系統廠商因爲能夠直接修改源碼,也利用這方面的便利,作一些功能和優化,不過因爲保密的問題,代碼就不直接放上來了,我能夠大概說一下思路,感興趣的能夠私下討論

移動事件優化

Choreographer 自己是沒有 input 消息的, 不過修改源碼以後,input 消息能夠直接給到 Choreographer 這裏, 有了這些 Input 消息,Choreographer 就能夠作一些事情,好比說提早響應,不去等 Vsync

後臺動畫優化

當一個 Android App 退到後臺以後,只要他沒有被殺死,那麼他作什麼事情你們都不要奇怪,由於這就是 Android。有的 App 退到後臺以後還在持續調用 Choreographer 中的 Animation Callback,而這個 Callback 的執行徹底是無心義的,並且用戶還不知道,可是對 cpu 的佔用是比較高的。

因此在 Choreographer 中會針對這種狀況作優化,禁止不符合條件的 App 在後臺繼續無用的操做

圖片

幀繪製優化

和移動事件優化同樣,因爲有了 Input 事件的信息,在某些場景下咱們能夠通知 SurfaceFlinger 不用取等待 Vsync 直接作合成操做

應用啓動優化

咱們前面說,主線程的全部操做都是給予 Message 的 ,若是某個操做,非重要的 Message 被排列到了隊列後面,那麼對這個操做產生影響;而經過從新排列 MessageQueue,在應用啓動的時候,把啓動相關的重要的啓動 Message 放到隊列前面,來起到加快啓動速度的做用

高幀率優化

90 fps 的手機上 , Vsync 間隔從 16.6ms 變成了 11.1ms ,這帶來了巨大的性能和功耗挑戰,如何在一幀內完成渲染的必要操做,是手機廠商必需要思考和優化的地方:

  1. 超級 App 的性能表現以及優化
  2. 遊戲高幀率合做
  3. 90 fps 和 60 fps 相互切換的邏輯

本文知乎地址

點贊或者交流,能夠移步本文的知乎界面 知乎 - Android 基於 Choreographer 的渲染機制詳解

關於我

小廠系統研發工程師 , 更多信息能夠點擊 關於我 , 很是但願和你們一塊兒交流 , 共同進步 .

參考資料

  1. www.jianshu.com/p/304f56f5d…
  2. gityuan.com/2017/02/25/…
  3. developer.android.com/reference/a…
  4. www.jishuwen.com/d/2Vcc
  5. juejin.im/entry/5c877…
相關文章
相關標籤/搜索