Android 騰訊 Matrix 原理分析(三):TracePlugin 卡頓分析之幀率監聽

前言

TracePlugin 卡頓分析插件中包含很多 Tracer,而 FrameTracer 負責監聽幀率。拿到產生的幀率數據之后,根據用戶設置的丟幀閾值進行報告。

那么 TracePlugin 是怎么拿到每一幀數據的?本篇文章將圍繞這個問題依據 Matrix 源碼進行解答。

一、準備工作

我在上篇文章中寫道,UIThreadMonitor 是 Tracer 工作功能實現的基礎,FrameTracer 也不例外。

UIThreadMonitor 可以監聽主線程 Looper 事件、接收硬件每 16ms 發送來的垂直同步 VSync 信號,而 FrameTracer 通過設置監聽到 UIThreadMonitor 接收到每幀刷新的回調方法 onFrame()

從 FrameTracer 的介紹我們知道,該類具有統計每幀數據的能力,而這些數據則是由 UIThreadMonitor 提供的。UIThreadMonitor 又是怎么做到的呢?接下來一起看看吧。

1.1 UIThreadMonitor 初始化

TracePlugin 在 Matrix init 之后由開發者手動啟動,TracePlugin 啟動后會初始化 UIThreadMonitor,執行它的 init() 方法:

UIThreadMonitor # init()

public class UIThreadMonitor implements BeatLifecycle, Runnable {
    
    private static final String ADD_CALLBACK = "addCallbackLocked";
    private Object callbackQueueLock;
    private Object[] callbackQueues;
    // 三種類型添加數據的方法
    private Method addTraversalQueue;
    private Method addInputQueue;
    private Method addAnimationQueue;
    private Choreographer choreographer;
    
    public void init(TraceConfig config) {
        if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
            throw new AssertionError("must be init in main thread!");
        }
        // 第一部分
        // getInstance() 是 ThreadLocal 實現,在主線程創建的,所以獲取的是主線程的 Choreographer
        choreographer = Choreographer.getInstance();
        // 獲取 Choreographer 的對象鎖
        callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object());
        // 獲取 Choreographer 的 mCallbackQueues 對象,也就是 CallbackQueues 數組
        callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null);
        if (null != callbackQueues) {
            // 獲取三種類型對象的 addCallbackLocked 方法
            addInputQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);
            addAnimationQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class);
            addTraversalQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class);
        }
        vsyncReceiver = ReflectUtils.reflectObject(choreographer, "mDisplayEventReceiver", null);
        frameIntervalNanos = ReflectUtils.reflectObject(choreographer, "mFrameIntervalNanos", Constants.DEFAULT_FRAME_DURATION);
    }
    // 第二部分
    LooperMonitor.register(new LooperMonitor.LooperDispatchListener() {
         @Override
         public boolean isValid() {
             return isAlive;
         }

         @Override
         public void dispatchStart() {
             super.dispatchStart();
             UIThreadMonitor.this.dispatchBegin();
         }

         @Override
         public void dispatchEnd() {
             super.dispatchEnd();
             UIThreadMonitor.this.dispatchEnd();
         }

     });
     this.isInit = true;
}

第一部分:反射 Choreographer

Choreographer.getInstance() 是 ThreadLocal 實現,而 UIThreadMonitor 是在主線程初始化的,所以獲取的是主線程的 Choreographer 對象。

Choreographer 在接收到 VSync 信號之后會通過內部的 Handler 發送一個異步消息執行 doFrame() 方法。由于是主線程的 Choreographer,所以執行 doFrame() 方法也是在主線程環境下完成的。

而在 doFrame() 方法中會遍歷 CallbackQueue 數組(Choreographer 內部維護的回調隊列)并回調它們的 doFrame()/run() 方法,Matrix 當然可以通過 Choreographer 的外部方法向 CallbackQueue 添加監聽,但是這樣做就只能接收到回調而不能達到監控的目的。

所以 Matrix 利用反射向 CallbackQueue 數組每個下標鏈表頭部添加回調,在每個類型回調之后更新狀態并統計耗時。

回過頭來看第一部分的代碼,第一部分要注意的是反射獲取的三個方法的邏輯:

  • callbackQueues 數組三個下標對應三種不同的回調類型對象:CALLBACK_INPUT(下標 0)、CALLBACK_ANIMATION(下標 1)、CALLBACK_TRAVERSAL(下標 2)。
  • 那么 callbackQueues[CALLBACK_INPUT]、callbackQueues[CALLBACK_ANIMATION]、callbackQueues[CALLBACK_TRAVERSAL] 獲取的就是這三種類型的對象實例。
  • 最后獲取它們的 addCallbackLocked() 方法,這個方法的作用是往當前鏈表添加元素。

接著看一下反射獲取到的方法原型:

Choreographer.CallbackQueue # addCallbackLocked()

public void addCallbackLocked(long dueTime, Object action, Object token) {
    // 緩存獲取,如果沒有則創建
    CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
    CallbackRecord entry = mHead;
    // 頭結點為 null 直接替代
    if (entry == null) {
        mHead = callback;
        return;
    }
    // 根據時間排序
    if (dueTime < entry.dueTime) {
        callback.next = entry;
        mHead = callback;
        return;
    }
    while (entry.next != null) {
        if (dueTime < entry.next.dueTime) {
            callback.next = entry.next;
            break;
        }
        entry = entry.next;
    }
    entry.next = callback;
}
  • CallbackRecord:所有的回調會被包裝為 CallbackRecord 類,該類會保存定時時間、回調、token,后文會再分析;
  • 后面的邏輯也比較簡單,就是往當前鏈表添加元素。注意如果傳入的定時時間 dueTime 小于頭結點的時間,則會替換頭結點。

這樣就 創建了 Choreographer 并提供了添加三種回調類型的方法。

第二部分:監聽主線程 Looper 事件前后

第二部分的代碼主要就是監聽主線程 Looper 處理每條消息前后,也就是說處理消息前回調 dispatchStart()、消息被 Handler 處理之后回調 dispatchEnd()

有關 UIThreadMonitor 和 LooperMonitor 的實現可以參考前文:

Android 騰訊 Matrix 原理分析(二):TracePlugin 卡頓分析之主線程監聽

2.2 準備數據容器

UIThreadMonitor 執行 init() 初始化之后,緊接著會調用 onStar() 方法啟動:

UIThreadMonitor # onStar()

public class UIThreadMonitor implements BeatLifecycle, Runnable {
    // 三種回調三個下標
    public static final int CALLBACK_INPUT = 0;
    public static final int CALLBACK_ANIMATION = 1;
    public static final int CALLBACK_TRAVERSAL = 2;
    // 回調的最大值
    private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL;
    // 創建數組存放狀態和花費時間
    private int[] queueStatus = new int[CALLBACK_LAST + 1];
    private long[] queueCost = new long[CALLBACK_LAST + 1];
    
    @Override
    public synchronized void onStart() {
        if (!isInit) {
            MatrixLog.e(TAG, "[onStart] is never init.");
            return;
        }
        if (!isAlive) {
            this.isAlive = true;
            synchronized (this) {
                MatrixLog.i(TAG, "[onStart] callbackExist:%s %s", Arrays.toString(callbackExist), Utils.getStack());
                callbackExist = new boolean[CALLBACK_LAST + 1];
            }
            // 1.狀態數組
            queueStatus = new int[CALLBACK_LAST + 1];
            // 2.花費時間數組
            queueCost = new long[CALLBACK_LAST + 1];
            addFrameCallback(CALLBACK_INPUT, this, true);
        }
    }
}

根據上篇文章可知,我們需要關注的回調類型有三種:CALLBACK_INPUT(輸入)CALLBACK_ANIMATION(動畫)CALLBACK_TRAVERSAL(繪制)

對于 Matrix 來說,要做的事情有兩件:

  1. 記錄每種回調的狀態,也就是數組 queueStatus,容量為 3(因為監聽三種回調嘛)。
    順帶提一下,數組 queueStatus 每個下標可以賦值為固定的狀態值:
private static final int DO_QUEUE_BEGIN = 1;
private static final int DO_QUEUE_END = 2;
  1. 記錄每種回調所花費的時間,數組 queueCost,容量同樣為 3。
    每個下標第一次記錄回調開始的時間,第二次計算出花費的時間并記錄。

這樣添加監聽系統信號的方法有了,儲存數據的容器也有了,就可以讓 FrameTracer 添加監聽并接收數據了。

二、FrameTracer 添加監聽

FrameTracer 是 TracePlugin(卡頓分析插件) 的一部分,所以也是在 TracePlugin 中創建和開始工作的:

TracePlugin # init & start() 簡略版

public class TracePlugin extends Plugin {
    private FrameTracer frameTracer;

    @Override
    public void init(Application app, PluginListener listener) {
        super.init(app, listener);
        // 1.初始化
        frameTracer = new FrameTracer(traceConfig);
    }
    
    @Override
    public void start() {
    super.start();
    Runnable runnable = new Runnable() {
         @Override
         public void run() {
             if (!UIThreadMonitor.getMonitor().isInit()) {
                 try {
                     UIThreadMonitor.getMonitor().init(traceConfig);
                 } catch (java.lang.RuntimeException e) {
                     MatrixLog.e(TAG, "[start] RuntimeException:%s", e);
                     return;
                 }
             }
             // 開始監聽主線程
             UIThreadMonitor.getMonitor().onStart();
             // 2.Tracer 開始工作
             frameTracer.onStartTrace();
         }
     };
    }
}
  1. new FrameTracer:FrameTracer 的構造器主要利用傳入的用戶配置 traceConfig 進行參數設置,然后添加一個 FPS 的監聽。后文可能還會分析,這里先不貼這段代碼。

  2. frameTracer.onStartTrace():TracePlugin 的 start() 方法是由開發者手動調用的,里面調用父類 Tracer 的onStartTrace() 標記 FrameTracer 進入活動狀態。

Tracer # onStartTrace()

@Override
final synchronized public void onStartTrace() {
    if (!isAlive) {
        this.isAlive = true;
        onAlive();
    }
}

緊接著調用 onAlive() 使子類 FrameTracer 開始工作:

FrameTracer # onAlive()

@Override
public void onAlive() {
    super.onAlive();
    UIThreadMonitor.getMonitor().addObserver(this);
}

可以看到 FrameTracer 進入活動狀態后只是把自己添加到 UIThreadMonitor 的監聽者列表中,添加的類型是 LooperObserver。

UIThreadMonitor # addObserver

public void addObserver(LooperObserver observer) {
    if (!isAlive) {
        onStart();
    }
    synchronized (observers) {
        observers.add(observer);
    }
}

因為父類 Tracer 實現了 LooperObserver 接口,所以可以被添加到監聽列表中,而添加監聽的目的就是為了接收每幀回調。

在第一節的準備中,UIThreadMonitor 已經擁有了監聽系統 VSync 信號的能力,只需要在接收到信號的時候回調這些監聽就可以讓 FrameTracer 接收到每幀的回調。

接下來看 UIThreadMonitor 是如何接收系統垂直同步信號并返回給監聽者的。

三、UIThreadMonitor 監聽幀率

3.1 監聽系統 VSync 信號

UIThreadMonitor 的 onStart() 方法中有一句重要代碼:

UIThreadMonitor # onStart()

@Override
public synchronized void onStart() {
    if (!isInit) {
        MatrixLog.e(TAG, "[onStart] is never init.");
        return;
    }
    if (!isAlive) {
        this.isAlive = true;
        synchronized (this) {
            MatrixLog.i(TAG, "[onStart] callbackExist:%s %s", Arrays.toString(callbackExist), Utils.getStack());
            callbackExist = new boolean[CALLBACK_LAST + 1];
        }
        queueStatus = new int[CALLBACK_LAST + 1];
        queueCost = new long[CALLBACK_LAST + 1];
        // 添加系統信號回調
        addFrameCallback(CALLBACK_INPUT, this, true);
    }
}

就是最后這句 addFrameCallback(CALLBACK_INPUT, this, true);注意此時第一個參數為 CALLBACK_INPUT 表示添加輸入類型的回調、第二個參數 this 也就是把 UIThreadMonitor 這個線程對象傳遞、第三個 true 表示添加到隊首。

接下來看是怎么添加的:

UIThreadMonitor # addFrameCallback()

private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
    if (callbackExist[type]) {
        MatrixLog.w(TAG, "[addFrameCallback] this type %s callback has exist! isAddHeader:%s", type, isAddHeader);
        return;
    }

    if (!isAlive && type == CALLBACK_INPUT) {
        MatrixLog.w(TAG, "[addFrameCallback] UIThreadMonitor is not alive!");
        return;
    }
    try {
        synchronized (callbackQueueLock) {
            Method method = null;
            // 1. 根據添加類型得到要調用的對象方法
            switch (type) {
                case CALLBACK_INPUT:
                    method = addInputQueue;
                    break;
                case CALLBACK_ANIMATION:
                    method = addAnimationQueue;
                    break;
                case CALLBACK_TRAVERSAL:
                    method = addTraversalQueue;
                    break;
            }
            if (null != method) {
                // 2. 調用相應對象添加元素的方法
                method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
                callbackExist[type] = true;
            }
        }
    } catch (Exception e) {
        MatrixLog.e(TAG, e.toString());
    }
}
  1. 首先根據傳入的回調類型確定調用的方法,方法對象在 init() 的時候已經創建好了,這里直接調用就可以了。
    比如這里傳入的是 CALLBACK_INPUT 類型,使用的是 addInputQueue 方法對象。
Method addInputQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);

這個 Method 對象由三部分組成:

  • callbackQueues[CALLBACK_INPUT]:對象實例,獲取 callbackQueues 第一個元素,類型為 CallbackQueue;
  • ADD_CALLBACK:方法名,表示調用的是上面對象的 addCallbackLocked 方法;
    private static final String ADD_CALLBACK = "addCallbackLocked";
  • long.class, Object.class, Object.class:方法參數,對象可能包含多個方法重載,所以傳入入參類型確定具體調用哪個方法。

CallbackQueue # addCallbackLocked

public void addCallbackLocked(long dueTime, Object action, Object token) {...}

  1. 確定好方法之后,就可以調用 invoke() 執行了,后面參數也可以分為三部分:
  • callbackQueues[type]:對象實例,傳來的是 CALLBACK_INPUT 也就是調用 callbackQueues 第一個對象的 addCallbackLocked() 方法;
  • !isAddHeader ? SystemClock.uptimeMillis() : -1:如果添加到鏈表頭傳 -1,反之傳入開機到當前的時間總數;
  • callback:接收系統信號的回調,這里傳入的是 this 表示由 UIThreadMonitor 接收。UIThreadMonitor 是一個線程,所以會回調它的 run() 方法。

回調添加完畢,接下來就等系統發出 VSync 信號了。

3.3 準備接收系統信號

ViewRootImp 在首次繪制或者子 View 們發生變化時會請求接收 VSync 信號,接著 Choreographer 就會收到硬件發來的信號。

Choreographer 收到系統的 VSync 信號之后,調用 doFrame() 遍歷回調數組:

Choreographer # doFrame()

void doFrame(long frameTimeNanos, int frame) {
     ...
     try {
         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);

         mFrameInfo.markPerformTraversalsStart();
         doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

         doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
     } finally {
         AnimationUtils.unlockAnimationClock();
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
     ...
 }

這段代碼要注意的是回調執行的順序,可以看到先是回調 CALLBACK_INPUT 類型的數據,等到遍歷回調完畢之后再遍歷后續的 CALLBACK_ANIMATIONCALLBACK_TRAVERSAL

而回調的方法也很簡單,就是生成 CallbackRecord 鏈表并遍歷執行 run() 方法:

Choreographer # doCallbacks()

void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
        final long now = System.nanoTime();
        // 1.遍歷 callbackType 類型鏈表,包裝成 CallbackRecord 鏈表
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
        mCallbacksRunning = true;
        ...
    }
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
        // 2.遍歷執行 run 方法
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
            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);
    }
}

CallbackRecord 的 run() 方法就是根據 token 來判斷執行 doFrame() 還是 run()

Choreographer.CallbackRecord

private static final class CallbackRecord {
    public CallbackRecord next;
    public long dueTime;
    public Object action; // Runnable or FrameCallback
    public Object token;
    // 調用 run 方法
    public void run(long frameTimeNanos) {
        if (token == FRAME_CALLBACK_TOKEN) {
            ((FrameCallback)action).doFrame(frameTimeNanos);
        } else {
            ((Runnable)action).run();
        }
    }
}

在 3.3 節中可知 UIThreadMonitor 傳入的 token 是 null,所以會回調它的 run()方法。

3.4 接收到信號之后

UIThreadMonitor # run()

@Override
public void run() {
    final long start = System.nanoTime();
    try {
        // 1.標記回調垂直同步
        doFrameBegin(token);
        // 2.記錄時間
        doQueueBegin(CALLBACK_INPUT);
        // 3.添加動畫類型回調
        addFrameCallback(CALLBACK_ANIMATION, new Runnable() {

            @Override
            public void run() {
                // 3.1CALLBACK_INPUT結束、CALLBACK_ANIMATION開始
                doQueueEnd(CALLBACK_INPUT);
                doQueueBegin(CALLBACK_ANIMATION);
            }
        }, true);
        // 4.添加繪制類型回調
        addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {

            @Override
            public void run() {
                // 4.1CALLBACK_ANIMATION結束、CALLBACK_TRAVERSAL開始
                doQueueEnd(CALLBACK_ANIMATION);
                doQueueBegin(CALLBACK_TRAVERSAL);
            }
        }, true);

    } finally {
        if (config.isDevEnv()) {
            MatrixLog.d(TAG, "[UIThreadMonitor#run] inner cost:%sns", System.nanoTime() - start);
        }
    }
}

private void doFrameBegin(long token) {
    // 垂直同步標記
    this.isVsyncFrame = true;
}
  1. doFrameBegin() 方法只是設置一個標記,用來表示當前接收到了垂直同步信號,該標記后面會有用處;
  2. 執行到 run() 方法說明 Choreographer 已經開始回調 CALLBACK_INPUT 類型的對象了,而 UIThreadMonitor 又被添加到了鏈表頭部,所以記錄時間作為 CALLBACK_INPUT 類型回調的起始;
private void doQueueBegin(int type) {
    // 記錄狀態
    queueStatus[type] = DO_QUEUE_BEGIN;
    // 記錄時間
    queueCost[type] = System.nanoTime();
}
  1. 接著添加 CALLBACK_ANIMATION 類型的回調,等待 Choreographer 的遍歷;
    3.1 等到開始回調 CALLBACK_ANIMATION 類型的數據,說明 CALLBACK_INPUT 已經全部回調完畢了,調用 doQueueEnd() 更新狀態記錄時間。
private void doQueueEnd(int type) {
    // 更新狀態
    queueStatus[type] = DO_QUEUE_END;
    // 當前時間 - 開始時記錄的時間 = 處理 type 類型花費的時間
    queueCost[type] = System.nanoTime() - queueCost[type];
    synchronized (this) {
        callbackExist[type] = false;
    }
}
  1. 同理,添加 CALLBACK_TRAVERSAL 類型的回調;
    4.1 等到開始執行 CALLBACK_TRAVERSAL 類型的回調,說明上一個狀態的回調已經遍歷執行完畢了,所以更改 CALLBACK_ANIMATION 的狀態并記錄所花費的時間。

小總結

這部分可能稍微有點繞,我們畫個圖來幫助理解:
添加回調流程
  1. UIThreadMonitor 添加 CALLBACK_INPUT 類型的回調到 Choreographer;
    addFrameCallback(CALLBACK_INPUT, this, true);
  2. Choreographer 接收到系統信號,遍歷 CALLBACK_INPUT 鏈表并執行回調;
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
  3. 此時 UIThreadMonitor 接收到回調,說明 CALLBACK_INPUT 類型的事件開始處理了,記錄為開始狀態并記錄開始時間;
    doQueueBegin(CALLBACK_INPUT);
    然后添加 CALLBACK_ANIMATION 類型的回調;
    addFrameCallback(CALLBACK_ANIMATION, new Runnable(){...},true);
  4. Choreographer 開始遍歷 CALLBACK_ANIMATION 鏈表,然后執行回調;
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
  5. CALLBACK_ANIMATION 類型接收到回調,說明上一個類型 CALLBACK_INPUT 事件處理完畢了,記錄為結束狀態并統計耗時;
    doQueueEnd(CALLBACK_INPUT);
    同時 CALLBACK_ANIMATION 事件開始處理了,記錄為開始狀態并記錄開始時間;
    doQueueBegin(CALLBACK_ANIMATION);
  6. 添加 CALLBACK_TRAVERSAL 類型的回調;
    addFrameCallback(CALLBACK_TRAVERSAL, new Runnable(){...},true);
  7. Choreographer 開始遍歷 CALLBACK_TRAVERSAL 鏈表,然后執行回調;
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
  8. CALLBACK_TRAVERSAL 類型接收到回調,說明 上一個類型CALLBACK_ANIMATION 的事件處理完畢了,記錄為結束狀態并統計耗時;
    doQueueEnd(CALLBACK_ANIMATION);
    同時 CALLBACK_TRAVERSAL 事件開始處理了,記錄為開始狀態并記錄開始時間;

到這里 ViewRootImp 三種類型的回調都已經執行了,UIThreadMonitor 也成功監聽并統計耗時。但是還有最后的問題,CALLBACK_TRAVERSAL 回調只有開始沒有結束。

要解決這個問題,需要了解一個大前提:三種類型的回調是在 Choreographer 的 doFrame() 方法中回調的,而 doFrame() 方法是由 Handler 發送線程來執行的。

Choreographer.FrameDisplayEventReceiver

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
        implements Runnable {
    ...
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        ...
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }

    @Override
    public void run() {
        doFrame(mTimestampNanos, mFrame);
    }
}

Handler 發送的事件是由 Looper 取出來處理的,如果能夠監聽 Looper 處理完這個事件了,說明 doFrame() 方法也執行完畢、 CALLBACK_TRAVERSA 也回調完畢了。

那么這個 Looper 處理事件結束時可以監聽到么?當然可以,就是上篇文章分析過的 LooperMonitor。

四、FrameTracer 接收數據

LooperMonitor 是在 UIThreadMonitor 初始化時添加監聽的:

UIThreadMonitor # init()

public void init(TraceConfig config) {
    ...
    LooperMonitor.register(new LooperMonitor.LooperDispatchListener() {
        @Override
        public boolean isValid() {
            return isAlive;
        }

        @Override
        public void dispatchStart() {
            super.dispatchStart();
            UIThreadMonitor.this.dispatchBegin();
        }

        @Override
        public void dispatchEnd() {
            super.dispatchEnd();
            UIThreadMonitor.this.dispatchEnd();
        }
    });
    ...
}

這樣就能夠監聽到 Looper 取出來的所有事件處理前后的消息,當然前提是在主線程環境下。

因為 UIThreadMonitor 是在主線程創建的,所以監聽的就是主線程的 Looper。

但是這里還有一個問題,主線程 Looper 所有事件都監聽,怎么才能確定是 VSync 信號事件呢?很簡單,在接收到 VSync 信號的第一個回調設置一個標記,就是前面出現過的 isVsyncFrame

可以看到 UIThreadMonitor 在接收到第一個回調之后,就標記 isVsyncFrame 為 true 說明現在處理的是 doFrame() 事件。

UIThreadMonitor # run()

@Override
public void run() {
    final long start = System.nanoTime();
    try {
        doFrameBegin(token);
        ...
    }
}

private void doFrameBegin(long token) {
    this.isVsyncFrame = true;
}

然后在 doFrame() 事件結束之后,給 LooperMonitor 設置的監聽會回調 dispatchEnd() 方法執行 UIThreadMonitor 的 dispatchEnd() 方法:

UIThreadMonitor # dispatchEnd()

private void dispatchEnd() {
    long traceBegin = 0;
    long startNs = token;
    long intendedFrameTimeNs = startNs;
    if (isVsyncFrame) {
        // 1.結束回調
        doFrameEnd(token);
        intendedFrameTimeNs = getIntendedFrameTimeNs(startNs);
    }
    // 2.通知監聽
    long endNs = System.nanoTime();
    synchronized (observers) {
         for (LooperObserver observer : observers) {
             if (observer.isDispatchBegin()) {
                 observer.doFrame(AppMethodBeat.getVisibleScene(), startNs, endNs, isVsyncFrame, intendedFrameTimeNs, queueCost[CALLBACK_INPUT], queueCost[CALLBACK_ANIMATION], queueCost[CALLBACK_TRAVERSAL]);
             }
         }
    }
    this.isVsyncFrame = false;
}
  1. 可以看到如果是 isVsyncFrame doFrame() 事件則調用 doFrameEnd() 結束 CALLBACK_TRAVERSA 事件:

UIThreadMonitor # doFrameEnd()

private void doFrameEnd(long token) {
    // 標記結束,統計耗時
    doQueueEnd(CALLBACK_TRAVERSAL);
    queueStatus = new int[CALLBACK_LAST + 1];
    // 設置新一輪的監聽
    addFrameCallback(CALLBACK_INPUT, this, true);
}
  • 到這個方法就結束了 CALLBACK_TRAVERSA 的回調,并且成功統計到了耗時;
  • doFrame() 可能是被持續調用的,因為 UI 發生變化每秒要刷新 60 次呢,所以需要添加新一輪的監聽。
    再次接收到 CALLBACK_INPUT 就再修改狀態、統計時間,添加另外兩種類型的回調。
  1. 一輪 doFrame() 執行完畢之后,就可以回調添加到 UIThreadMonitor 的監聽來統計信息了。而 FrameTracer 就是眾多監聽者的一員。
    UIThreadMonitor 遍歷所有 LooperObserver 并執行它們的 doFrame() 方法并傳遞參數,FrameTracer 通過 doFrame() 方法就可以拿到數據展示信息了。

FrameTracer # doFrame()

@Override
public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {
    if (isForeground()) {
        notifyListener(focusedActivity, startNs, endNs, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
    }
}

到這里 FrameTracer 終于接收到了數據,依據這些數據就可以進行幀率統計、幀率圖繪制等工作了。

總結

簡單總結下本篇文章記錄的內容:

  1. FrameTracer 為了接收三種事件處理的時間和幀率,向 UIThreadMonitor 添加監聽;
  2. UIThreadMonitor 內部獲取主線程 Choreographer 用于接收垂直同步信號,同時反射獲取向 Choreographer 數組添加回調方法的對象;
  3. UIThreadMonitor 初始化時向 Choreographer 數組添加回調,等 Choreographer 回調數組之后記錄每種回調的狀態和耗時;
  4. 最后 UIThreadMonitor 將回調得來的數據返回給監聽者,其中就包含 FrameTracer。
  5. FrameTracer 拿到數據之后在作處理,鑒于篇幅,FrameTracer 的具體邏輯將在下篇文章進行分析。

為了得到 FrameTracer 要使用的數據,使用了 UIThreadMonitorLooperMonitorChoreographer 等,真是不容易呀。

對于 Matrix 來說,UIThreadMonitor 是很重要的部分。幀率、慢函數、ANR 監控等都用到了它,看懂 UIThreadMonitor 的實現對理解 Matrix 框架會有很大幫助。

最后感謝大家的閱讀。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容