经过前两篇文章的学习,我们知道UIThreadMonitor 可以监听主线程 Looper 事件 。dispatchBegin dispatchEnd是怎么来的。通过学习Choreographer的作用和大致原理,我们知道了 接收硬件每 16ms 发送来的垂直同步 VSync 信号,监控帧率的办法。
具备这些知识以后我们就可以分析 UIThreadMonitor 中的最后一块代码 choreographer。接下来一起看看吧。
TracePlugin 在 Matrix init 之后由开发者手动启动,TracePlugin 启动后会初始化 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!");
}
// 针对于系统26以下所采用的办法,更高API的其他方法后序讲解
// 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);
}
//监听Looper
this.isInit = true;
}
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() 方法,这个方法的作用是往当前链表添加元素。
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 处理每条消息前后,也就是说处理消息前回调 dispatchStart()、消息被 Handler 处理之后回调 dispatchEnd()。
有关 UIThreadMonitor 和 LooperMonitor 的实现可以参考前文: 腾讯性能监控框架Matrix源码分析(四)TracePlugin 卡顿ANR监控
UIThreadMonitor 执行 init() 初始化之后,紧接着会调用 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;
2 记录每种回调所花费的时间,数组 queueCost,容量同样为 3。
每个下标第一次记录回调开始的时间,第二次计算出花费的时间并记录。
这样添加监听系统信号的方法有了,储存数据的容器也有了,那么如何计算花费时间呢?
onStart中的最后一句话 addFrameCallback(CALLBACK_INPUT, this, true);,注意此时第一个参数为 CALLBACK_INPUT 表示添加输入类型的回调、第二个参数 this 也就是把 UIThreadMonitor 这个线程对象传递、第三个 true 表示添加到队首。
我们看具体添加方法
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:方法参数,对象可能包含多个方法重载,所以传入参类型确定具体调用哪个方法。
确定好方法之后,就可以调用 invoke() 执行了,后面参数也可以分为三部分:
- callbackQueues[type]:对象实例,传来的是 CALLBACK_INPUT 也就是调用 callbackQueues 第一个对象的 addCallbackLocked() 方法;
- !isAddHeader ? SystemClock.uptimeMillis() : -1:如果添加到链表头传 -1,反之传入开机到当前的时间总数;
- callback:接收系统信号的回调,这里传入的是 this 表示由 UIThreadMonitor 接收。UIThreadMonitor 是一个线程,所以会回调它的 run() 方法。
回调添加完毕,接下来就等系统发出 VSync 信号了。
ViewRootImp 在首次绘制或者子 View 们发生变化时会请求接收 VSync 信号,接着 Choreographer 就会收到硬件发来的信号。
Choreographer 收到系统的 VSync 信号之后,调用 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() 方法:
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()。
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();
}
}
}
前面的章节可知UIThreadMonitor 传入的 token 是 null,所以会回调它的 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 的遍历; 等到开始回调 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 类型的回调; 等到开始执行 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 发送线程来执行的。
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。
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() 事件。
@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() 方法:
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 事件:
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 为了接收三种事件处理的时间和帧率,向 UIThreadMonitor 添加监听;
- UIThreadMonitor 内部获取主线程 Choreographer 用于接收垂直同步信号,同时反射获取向 Choreographer 数组添加回调方法的对象;
- UIThreadMonitor 初始化时向 Choreographer 数组添加回调,等 Choreographer 回调数组之后记录每种回调的状态和耗时;
- 最后 UIThreadMonitor 将回调得来的数据返回给监听者,其中就包含 LooperAnrTracer FrameTracer等
鉴于篇幅,FrameTracer 的具体逻辑将在后序文章进行分析
为了得到 FrameTracer 要使用的数据,使用了 UIThreadMonitor、LooperMonitor、Choreographer 等,真是不容易呀。
对于 Matrix 来说,UIThreadMonitor 是很重要的部分。帧率、慢函数、ANR 监控等都用到了它,看懂 UIThreadMonitor 的实现对理解 Matrix 框架会有很大帮助。
你的 点赞、评论,是对我的巨大鼓励!