前言
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 的實現可以參考前文:
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 來說,要做的事情有兩件:
- 記錄每種回調的狀態,也就是數組
queueStatus
,容量為 3(因為監聽三種回調嘛)。
順帶提一下,數組queueStatus
每個下標可以賦值為固定的狀態值:
private static final int DO_QUEUE_BEGIN = 1;
private static final int DO_QUEUE_END = 2;
- 記錄每種回調所花費的時間,數組
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();
}
};
}
}
new FrameTracer
:FrameTracer 的構造器主要利用傳入的用戶配置traceConfig
進行參數設置,然后添加一個 FPS 的監聽。后文可能還會分析,這里先不貼這段代碼。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());
}
}
- 首先根據傳入的回調類型確定調用的方法,方法對象在
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) {...}
- 確定好方法之后,就可以調用
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_ANIMATION 和 CALLBACK_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;
}
-
doFrameBegin()
方法只是設置一個標記,用來表示當前接收到了垂直同步信號,該標記后面會有用處; - 執行到
run()
方法說明 Choreographer 已經開始回調 CALLBACK_INPUT 類型的對象了,而 UIThreadMonitor 又被添加到了鏈表頭部,所以記錄時間作為 CALLBACK_INPUT 類型回調的起始;
private void doQueueBegin(int type) {
// 記錄狀態
queueStatus[type] = DO_QUEUE_BEGIN;
// 記錄時間
queueCost[type] = System.nanoTime();
}
- 接著添加 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;
}
}
- 同理,添加 CALLBACK_TRAVERSAL 類型的回調;
4.1 等到開始執行 CALLBACK_TRAVERSAL 類型的回調,說明上一個狀態的回調已經遍歷執行完畢了,所以更改 CALLBACK_ANIMATION 的狀態并記錄所花費的時間。
小總結
這部分可能稍微有點繞,我們畫個圖來幫助理解:- UIThreadMonitor 添加 CALLBACK_INPUT 類型的回調到 Choreographer;
addFrameCallback(CALLBACK_INPUT, this, true);
- Choreographer 接收到系統信號,遍歷 CALLBACK_INPUT 鏈表并執行回調;
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
- 此時 UIThreadMonitor 接收到回調,說明 CALLBACK_INPUT 類型的事件開始處理了,記錄為開始狀態并記錄開始時間;
doQueueBegin(CALLBACK_INPUT);
然后添加 CALLBACK_ANIMATION 類型的回調;
addFrameCallback(CALLBACK_ANIMATION, new Runnable(){...},true);
- Choreographer 開始遍歷 CALLBACK_ANIMATION 鏈表,然后執行回調;
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
-
CALLBACK_ANIMATION 類型接收到回調,說明上一個類型 CALLBACK_INPUT 事件處理完畢了,記錄為結束狀態并統計耗時;
doQueueEnd(CALLBACK_INPUT);
同時 CALLBACK_ANIMATION 事件開始處理了,記錄為開始狀態并記錄開始時間;
doQueueBegin(CALLBACK_ANIMATION);
- 添加 CALLBACK_TRAVERSAL 類型的回調;
addFrameCallback(CALLBACK_TRAVERSAL, new Runnable(){...},true);
- Choreographer 開始遍歷 CALLBACK_TRAVERSAL 鏈表,然后執行回調;
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
-
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;
}
- 可以看到如果是 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 就再修改狀態、統計時間,添加另外兩種類型的回調。
- 一輪
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 終于接收到了數據,依據這些數據就可以進行幀率統計、幀率圖繪制等工作了。
總結
簡單總結下本篇文章記錄的內容:
- FrameTracer 為了接收三種事件處理的時間和幀率,向 UIThreadMonitor 添加監聽;
- UIThreadMonitor 內部獲取主線程 Choreographer 用于接收垂直同步信號,同時反射獲取向 Choreographer 數組添加回調方法的對象;
- UIThreadMonitor 初始化時向 Choreographer 數組添加回調,等 Choreographer 回調數組之后記錄每種回調的狀態和耗時;
- 最后 UIThreadMonitor 將回調得來的數據返回給監聽者,其中就包含 FrameTracer。
- FrameTracer 拿到數據之后在作處理,鑒于篇幅,FrameTracer 的具體邏輯將在下篇文章進行分析。
為了得到 FrameTracer 要使用的數據,使用了 UIThreadMonitor、LooperMonitor、Choreographer 等,真是不容易呀。
對于 Matrix 來說,UIThreadMonitor 是很重要的部分。幀率、慢函數、ANR 監控等都用到了它,看懂 UIThreadMonitor 的實現對理解 Matrix 框架會有很大幫助。
最后感謝大家的閱讀。