前言
UIThreadMonitor也是TracePlugin中的一个基础的能力,在分析其他tracer之前,有必要先将这些基础能力理解清楚,再进行后续的分析才能事半功倍。
UIThreadMonitors实现了两个接口:BeatLifecycle、Runnable,所以它是有生命周期的,同时也能运行在子线程中。我们从他几个关键的方法入手来查看源码。
init
方法初始化时,主要做了两件事:
- 向LooperMonitor注册监听,以拿到消息队列中消息执行前后的回调。LooperMonitor的实现可查阅Android性能优化系列-腾讯matrix-TracePlugin之LooperMonitor源码分析。
- 8.0以下通过反射的方式获取到Choreographer类中CallbackQueues数组相关的信息备用。 获取到的关键信息如下:
diff
- Choreographer对象
- Choreographer中的锁对象:mLock
- Choreographer中的CallbackQueue数组:mCallbackQueues
- Choreographer中的FrameDisplayEventReceiver对象:vsyncReceiver
- CallbackQueue数组中CALLBACK_INPUT类型的CallbackQueue对象的addCallbackLocked方法
- CallbackQueue数组中CALLBACK_ANIMATION类型的CallbackQueue对象的addCallbackLocked方法
- CallbackQueue数组中CALLBACK_TRAVERSAL类型的CallbackQueue对象的addCallbackLocked方法
注意,在UIThreadMonitor中,useFrameMetrics我们默认认为其值为false,因为UIThreadMonitor本身就是针对useFrameMetrics为false时的处理方案
。
ini
public void init(TraceConfig config, boolean supportFrameMetrics) {
//android N及以上使用Android提供了原生方法用于获取帧刷新的信息
//addOnFrameMetricsAvailableListener,但是细心的你会发现,matrix
//中Android O及以上supportFrameMetrics才设置为true?
useFrameMetrics = supportFrameMetrics;
LooperMonitor.register(new LooperMonitor.LooperDispatchListener(historyMsgRecorder, denseMsgTracer) {
...
});
//mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//1s = 1000000000ns, mFrameIntervalNanos表示刷新一次消耗的纳秒值,
//假如刷新率为60,那么mFrameIntervalNanos = 16666666纳秒
frameIntervalNanos = ReflectUtils.reflectObject(choreographer, "mFrameIntervalNanos", Constants.DEFAULT_FRAME_DURATION);
if (!useFrameMetrics) {
//8.0以下,反射得到Choreographer中的几个关键对象的方法
choreographer = Choreographer.getInstance();
callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object());
//反射得到mCallbackQueues数组
callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null);
if (null != callbackQueues) {
//反射得到mCallbackQueues数组中不同类型对象的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);
}
//choreographer中的FrameDisplayEventReceiver对象,用于接收vsync信号
vsyncReceiver = ReflectUtils.reflectObject(choreographer, "mDisplayEventReceiver", null);
}
}
onStart
初始化完成之后,开始onStart的调用,初始化了三个数组,然后调用addFrameCallback,将CALLBACK_INPUT类型的一个Runnable添加CALLBACK_INPUT类型的CallbackQueue中,并且是添加到头部位置。
arduino
@Override
public synchronized void onStart() {
synchronized (this) {
//用来记录指定type类型的runnable是否添加过,如添加过,不会重复添加
callbackExist = new boolean[CALLBACK_LAST + 1];
}
if (!useFrameMetrics) {
//两个数组,记录状态和时间,后边会用到
queueStatus = new int[CALLBACK_LAST + 1];
queueCost = new long[CALLBACK_LAST + 1];
//往CallbackQueue中添加
addFrameCallback(CALLBACK_INPUT, this, true);
}
}
addFrameCallback
此方法的作用是调用初始化时反射获取到的addCallbackLocked方法:
- addInputQueue
- addAnimationQueue
- addTraversalQueue
这三个Method都是addCallbackLocked的方法,只不过是不同对象的addCallbackLocked。 这里最先通过反射执行的是CALLBACK_INPUT类型的CallbackQueue的addCallbackLocked方法,会将当前Runnable(UIThreadMonitors本身就实现了Runnable接口)添加到系统Choreographer中的对应type类型的CallbackQueue中。
java
private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
synchronized (callbackQueueLock) {
Method method = null;
switch (type) {
//CALLBACK_INPUT类型
case CALLBACK_INPUT:
method = addInputQueue;
break;
//CALLBACK_ANIMATION类型
case CALLBACK_ANIMATION:
method = addAnimationQueue;
break;
//CALLBACK_TRAVERSAL类型
case CALLBACK_TRAVERSAL:
method = addTraversalQueue;
break;
}
if (null != method) {
//反射执行addCallbackLocked方法,将runnable添加进去
method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
}
}
}
addCallbackLocked
CallbackQueue是一个队列,队列内部存储的是CallbackRecord,CallbackRecord这里可以简单的认为是包装了Runnable的一个类,我们看下addCallbackLocked方法。
ini
public void addCallbackLocked(long dueTime, Object action, Object token) {
//将Runnable等信息封装成CallbackRecord对象
CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
CallbackRecord entry = mHead;
//链表为空,设置为表头
if (entry == null) {
mHead = callback;
return;
}
//将CallbackRecord插入到链表头位置,因为它时间比当前链表头时间小
if (dueTime < entry.dueTime) {
callback.next = entry;
mHead = callback;
return;
}
//将CallbackRecord插入到链表中,按照dueTime时间大小排序
while (entry.next != null) {
if (dueTime < entry.next.dueTime) {
callback.next = entry.next;
break;
}
entry = entry.next;
}
entry.next = callback;
}
那么这里在UIThreadMonitor onStart的时候往CALLBACK_INPUT类型的CallbackQueue队列中插入一个Runnable目的是什么?matrix想做什么?这里需要理解一些Choreographer的机制,简单提一下,详细内容可自行搜索。
Choreographer运行机制
Android的消息的运行依赖于VSYNC信号,每当新的VSYNC信号到达时,系统会一次性批量处理一些消息任务。而VSYNC信号的申请和接收是由Choreographer来管理的,我们直接进入VSYNC信号到达的位置看下代码,找到doFrame方法,这里只保留了与本次相关的逻辑。
scss
void doFrame(long frameTimeNanos, int frame) {
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
可以看出doCallbacks传递的第一个参数似乎与上边提到的几种类型的type相呼应了,没错,它们表示的其实是同一个意思,这里提到了五种type,而matrix中关注的只有其中三种。我们看下doCallbacks的实现。
java
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
//拿到对应type的CallbackRecord
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
}
try {
//遍历这个链表,所有被遍历到的CallbackRecord执行其run方法
for (CallbackRecord c = callbacks; c != null; c = c.next) {
c.run(frameTimeNanos);
}
} finally {
}
}
可以看出,doCallbacks是按照当前时间从CallbackQueues中拿到一个CallbackRecord作为入口,遍历这个链表,所有被遍历到的CallbackRecord执行其run方法,这也就是上边我们提到的"VSYNC信号到达时,系统会一次性批量处理一些消息任务"。所以按照顺序,是先执行CALLBACK_INPUT类型的任务,再执行CALLBACK_ANIMATION类型的任务,下一步执行CALLBACK_TRAVERSAL类型的任务,不同类型的任务按照优先级依次排队执行。
run
继续回到上边对addFrameCallback的分析中,在onStart的时候将一个Runnable插入到CALLBACK_INPUT类型的CallbackQueues队列的最靠前的位置,那么当下一次VSYNC信号到来的时候,这个Runnable会在所有CALLBACK_INPUT类型的任务中第一个被执行,于是就走到了这里的run方法。
scss
public void run() {
//此方法执行时,说明vsync信号到达,主线程已经开始处理input类型的第一条消息
final long start = System.nanoTime();
try {
//将isVsyncFrame设置为true,token是时间戳,记录消息队列当前消息执行前的那个时间节点
doFrameBegin(token);
//这里用到了onStart中初始化的两个数组,用来记录CALLBACK_INPUT类型消息开始的时间
doQueueBegin(CALLBACK_INPUT);
//调用addFrameCallback将CALLBACK_ANIMATION类型的runnable加入CALLBACK_ANIMATION
//类型的队列中,这样,当CALLBACK_INPUT类型的消息全部执行完时,就会执行到下面这个runnable,并将这个节点作为CALLBACK_INPUT类型消息结束的时机,记录这一vsync信号期间,
//所有CALLBACK_INPUT类型消息执行消耗的时长。
addFrameCallback(CALLBACK_ANIMATION, new Runnable() {
@Override
public void run() {
//input类型消息执行完成
doQueueEnd(CALLBACK_INPUT);
//记录CALLBACK_ANIMATION开始执行
doQueueBegin(CALLBACK_ANIMATION);
}
}, true);
//调用addFrameCallback将CALLBACK_TRAVERSAL类型的runnable加入CALLBACK_TRAVERSAL
//类型的队列中,这样,当CALLBACK_ANIMATION类型的消息全部执行完时,就会执行到下面这个runnable,并将这个节点作为CALLBACK_ANIMATION类型消息结束的时机,记录这一vsync信号期间,
//所有CALLBACK_ANIMATION类型消息执行消耗的时长。
addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {
@Override
public void run() {
//CALLBACK_ANIMATION类型消息执行完成
doQueueEnd(CALLBACK_ANIMATION);
//记录CALLBACK_TRAVERSAL开始执行
doQueueBegin(CALLBACK_TRAVERSAL);
}
}, true);
} finally {
}
}
按照注释中的分析,一个VSYNC信号期间,input类型消息、animation类型消息和traversal类型消息执行分别消耗了多少时间就统计出来了。但是你发现CALLBACK_TRAVERSAL只有开始,没有结束,怎么统计时间的?因为CALLBACK_TRAVERSAL类型消息执行完时,就表示当前VSYNC执行完成了,此时没有办法通过相同的方式记录结束的节点,于是在另一个节点做的记录-doFrameEnd方法中,doFrameEnd是在dispatchEnd被调用,dispatchEnd又是在LooperMonitor回调中执行的,从Android性能优化系列-腾讯matrix-TracePlugin之LooperMonitor源码分析中分析可知,LooperMonitor会将消息队列中每一消息执行前后的回调给到监听者,dispatchEnd就是消息执行完的时机,所以用来记录CALLBACK_TRAVERSAL执行完的时机也非常合适。
arduino
private void doFrameEnd(long token) {
doQueueEnd(CALLBACK_TRAVERSAL);
//上一VSYNC周期结束了,再添加一个CALLBACK_INPUT,如此周而复始,整个消息运行
//的时间就清晰的记录了下来
addFrameCallback(CALLBACK_INPUT, this, true);
}
typescript
private void doQueueBegin(int type) {
//指定type开始
queueStatus[type] = DO_QUEUE_BEGIN;
//记录开始的时间
queueCost[type] = System.nanoTime();
}
typescript
private void doQueueEnd(int type) {
//指定type结束
queueStatus[type] = DO_QUEUE_END;
//记录type类型消息消耗的时长
queueCost[type] = System.nanoTime() - queueCost[type];
synchronized (this) {
callbackExist[type] = false;
}
}
onStop
onStop切换状态。
java
@Override
public synchronized void onStop() {
if (!isInit) {
return;
}
if (isAlive) {
this.isAlive = false;
}
}
LooperMonitor的两个回调方法也是很关键的逻辑,这里我们顺着这两个回调再看一下相关源码。
dispatchBegin
这里会回调dispatchBegin方法给外界,传递相关参数。
scss
private void dispatchBegin() {
//记录消息开始分发的时间。System.nanoTime():从设备开机到现在的时间,单位毫秒,
//不含系统深度睡眠时间
token = dispatchTimeMs[0] = System.nanoTime();
//记录当前线程总共运行的时间,单位毫秒
dispatchTimeMs[2] = SystemClock.currentThreadTimeMillis();
if (config.isAppMethodBeatEnable()) {
//主动调一下i方法,内部会更新时间偏移量,参考:https://juejin.cn/post/7278883051380408356
AppMethodBeat.i(AppMethodBeat.METHOD_ID_DISPATCH);
}
//分发给所有监听者
synchronized (observers) {
for (LooperObserver observer : observers) {
if (!observer.isDispatchBegin()) {
observer.dispatchBegin(dispatchTimeMs[0], dispatchTimeMs[2], token);
}
}
}
}
dispatchEnd
此方法中会回调doFrame和dispatchEnd方法给外界,并传递相关参数。
scss
private void dispatchEnd() {
if (config.isFPSEnable() && !useFrameMetrics) {
long startNs = token;
long intendedFrameTimeNs = startNs;
if (isVsyncFrame) {
//记录traversal执行完成,并开启下一个周期
doFrameEnd(token);
intendedFrameTimeNs = getIntendedFrameTimeNs(startNs);
}
long endNs = System.nanoTime();
synchronized (observers) {
for (LooperObserver observer : observers) {
if (observer.isDispatchBegin()) {
//一帧结束。回调doFrame
//注意餐素比较关键
//1.当前可见的页面
//2.消息执行前的时间点
//3.消息执行完成后的时间点
//4.是否是根据vsync计算
//5.onVsync方法回调时的时间点
//6.input消耗的时长
//7.animation消耗的时长
//8.traversal消耗的时长
observer.doFrame(AppActiveMatrixDelegate.INSTANCE.getVisibleScene(), startNs, endNs, isVsyncFrame, intendedFrameTimeNs, queueCost[CALLBACK_INPUT], queueCost[CALLBACK_ANIMATION], queueCost[CALLBACK_TRAVERSAL]);
}
}
}
}
//慢方法记录开启的话,记录线程执行时间和当前执行时间
if (config.isEvilMethodTraceEnable() || config.isDevEnv()) {
dispatchTimeMs[3] = SystemClock.currentThreadTimeMillis();
dispatchTimeMs[1] = System.nanoTime();
}
//主动调用o方法
AppMethodBeat.o(AppMethodBeat.METHOD_ID_DISPATCH);
//回调消息分发结束
synchronized (observers) {
for (LooperObserver observer : observers) {
if (observer.isDispatchBegin()) {
//参数
//1.dispathStart开始的时间戳
//2.线程执行(cpu执行)开始的时间
//3.dispatchEnd结束时的时间戳
//4.线程执行(cpu执行)结束的时间
//5.token等于dispatchTimeMs[0]
//4和2两个参数可以计算出cpu运行的时长,3和1可以计算出总时长
observer.dispatchEnd(dispatchTimeMs[0], dispatchTimeMs[2], dispatchTimeMs[1], dispatchTimeMs[3], token, isVsyncFrame);
}
}
}
this.isVsyncFrame = false;
}
总结
由上边的分析可知,UIThreadMonitors总体上就是对LooperMonitor的封装与增强,它借助了LooperMonitor两个回调,dispatchBegin和dispatchEnd,并在这两个回调的基础上,丰富了各种参数,如消息执行总时间、消息执行期间cpu运行的时间,一个消息中input时间消耗的时间、animation消耗的时间、traversal消耗的时间等等,方便后续进行更细致的分析。