这是性能优化系列之matrix框架的第7
篇文章,我将在性能优化专栏中对matrix apm框架做一个全面的代码分析,性能优化是Android高级工程师必知必会的点,也是面试过程中的高频题目,对性能优化感兴趣的小伙伴可以去我主页查看所有关于matrix的分享。
前言
LooperMonitor是TracePlugin运行的基础能力之一。它通过向应用主线程的Looper设置Printer对象来实现对主线程消息队列运行情况进行监听,从而发现主线程运行卡顿的问题。今天我们来分析下它的内部实现,为后边分析TracePlugin中各种tracer打下基础。
创建
LooperMonitor对象是在类加载时创建的,在LooperMonitor类内部有一个静态变量,通过调用LooperMonitor.of来创建对象,这里以主线程Looper为key,以LooperMonitor对象为value保存在map中。
private static final LooperMonitor sMainMonitor = LooperMonitor.of(Looper.getMainLooper());
public static LooperMonitor of(@NonNull Looper looper) {
//以主线程Looper为key,以LooperMonitor对象为value保存在map中。
LooperMonitor looperMonitor = sLooperMonitorMap.get(looper);
if (looperMonitor == null) {
looperMonitor = new LooperMonitor(looper);
sLooperMonitorMap.put(looper, looperMonitor);
}
return looperMonitor;
}
构造方法
LooperMonitor的构造方法主要做了两件事,向Looper内设置Printer,然后将自己作为IdleHandler添加到消息队列中。
private LooperMonitor(Looper looper) {
Objects.requireNonNull(looper);
this.looper = looper;
//调用looper对象的setMessageLogging将Printer对象设置给Looper
resetPrinter();
//将自身添加到消息队列中的IdleHandler集合中,所以当消息队列空闲的时候,会执行每个IdleHandler的queueIdle方法
addIdleHandler(looper);
}
resetPrinter
省略了一些细节代码(判断当前printer有没有被替换过,一个looper只有一个printer对象,不能保证没有其他库不会使用同样的方式设置printer,所以printer是有较大概率被更改的)。核心逻辑就是调用looper对象的setMessageLogging将Printer对象设置给Looper,这样一来,LooperMonitor就可以拿到每隔消息执行前后的回调。
private synchronized void resetPrinter() {
try {
if (!isReflectLoggingError) {
...
}
} catch (Exception e) {
isReflectLoggingError = true;
}
//调用looper对象的setMessageLogging将Printer对象设置给Looper
looper.setMessageLogging(printer = new LooperPrinter(originPrinter));
}
}
为什么将Printer对象设置给Looper,LooperMonitor就可以拿到每隔消息执行前后的回调?
这涉及到Looper、消息队列的实现逻辑,简单提一下,我们知道,Android主线程的消息运行机制是由MessageQueue、looper、Handler、Message四个成员组成的,由Looper负责从MessageQueue中去粗Message消息,交给Handler进行处理。Looper取消息的方法是loop,我们看看loop的实现。
public static void loop() {
for (;;) {
Message msg = queue.next(); // might block
final Printer logging = me.mLogging;
if (logging != null) {
//调用logging.println进行日志打印
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
try {
//Handler处理消息的方法dispatchMessage
msg.target.dispatchMessage(msg);
} finally {
}
if (logging != null) {
//调用logging.println进行日志打印
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
在Handler处理消息的方法dispatchMessage前后,会判断假如me.mLogging存在,则调用logging.println进行日志打印。通过设置Printer之后,这两个调用就都在自己的控制中了。
LooperPrinter
resetPrinter方法给Looper设置的Printer对象是LooperPrinter,看看它的实现。
class LooperPrinter implements Printer {
@Override
public void println(String x) {
//origin是指matrix设置printer之前,就已经存在的printer,这个printer
//可能是其他库或者其他代码添加的,为了不影响其他逻辑的执行,这里并不会将其他
//的printer废弃掉,而是转发一下,这个细节处理很严谨。
if (null != origin) {
origin.println(x);
}
if (isValid) {
dispatch(x.charAt(0) == '>', x);
}
}
}
dispatch
拿到所有的监听者,给他们回调onDispatchStart和onDispatchEnd方法,这样一来,监听者都可以拿到消息执行开始和执行结束的回调,从而进行下一步处理。
//简化后的代码
private void dispatch(boolean isBegin, String log) {
for (LooperDispatchListener listener : listeners) {
//回调onDispatchStart和onDispatchEnd方法
if (isBegin) {
listener.onDispatchStart(log);
} else {
listener.onDispatchEnd(log);
}
}
}
上边简化后的代码是为了让读者清晰的看到主线逻辑,其实在这块代码中,还有一项比较重要的内容。如下,historyMsgRecorder表示是否记录历史消息,当它为true时,会执行recordMsg方法。
if (listener.historyMsgRecorder) {
//记录的是当前时间最近执行的message的信息
recordMsg(log, System.currentTimeMillis() - messageStartTime, listener.denseMsgTracer);
}
recordMsg
recordMsg(final String log, final long duration, boolean denseMsgTracer)
先看下方法定义,
- 第一个参数表示printer打印的日志,如:
">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what
- 第二个参数表示当前message执行的耗时时长,也就是handler的dispatchMessage执行的时长。
- 第三个参数是配置进来的,暂时看不出来,继续往下。
private static void recordMsg(final String log, final long duration, boolean denseMsgTracer) {
historyMsgHandler.post(new Runnable() {
@Override
public void run() {
//一个队列,最大容量200
enqueueHistoryMQ(new M(log, duration));
}
});
if (denseMsgTracer) {
historyMsgHandler.post(new Runnable() {
@Override
public void run() {
//最大容量5000,记录当前时间最近执行的5000个message的信息
enqueueRecentMQ(new M(log, duration));
}
});
}
}
historyMsgHandler是子线程的handler,post是将runnable发送到HandlerThread的子线程消息队列执行。这里被执行的有两个方法。
enqueueHistoryMQ
可以看到将当前message执行的时长等信息封装成一个M对象,存储在anrHistoryMQ中,anrHistoryMQ是一个队列,最大容量200,从其命名上可见是为anr服务的。记录的是当前时间最近执行的200个message的信息。
private static void enqueueHistoryMQ(M m) {
if (anrHistoryMQ.size() == HISTORY_QUEUE_MAX_SIZE) {
//超过容量,移除
anrHistoryMQ.poll();
}
anrHistoryMQ.offer(m);
}
enqueueRecentMQ
这里是将M对象存入了另一个队列recentMsgQ,它的命名翻译为最近的消息队列,最大容量5000,记录当前时间最近执行的5000个message的信息,可能也是因为消息容量大,对内存影响较大,所以matrix将其默认关闭,由使用者选择开启。上边我们不清楚denseMsgTracer的含义,这下明白了,dense意为密集的,就是说开启这个开关后,会记录5000条最近的消息执行信息,确实是密集的消息记录。
private static void enqueueRecentMQ(M m) {
if (recentMsgQ.size() == RECENT_QUEUE_MAX_SIZE) {
//超过容量,移除
recentMsgQ.poll();
}
//最大容量5000
recentMsgQ.offer(m);
recentMDuration += m.d;
}
有了这两个消息队列记录的信息,外界有需要的时候就可以自取了,通过LooperMonitor提供的两个方法 getHistoryMQ、getRecentMsgQ去拿,至于拿了之后做什么,不在本次分析范围之内,后边单独会提到。
addIdleHandler
LooperMonitor实现了MessageQueue.IdleHandler,并将自身添加到消息队列中的IdleHandler集合中,所以当消息队列空闲的时候,会执行每个IdleHandler的queueIdle方法。这里可以看到,每大于1分钟,LooperMonitor就会尝试resetPrinter(如果printer没有发生改变则什么都不做),主要是担心printer被其他逻辑覆盖掉导致无法运行。
public boolean queueIdle() {
if (SystemClock.uptimeMillis() - lastCheckPrinterTime >= CHECK_TIME) {
//这里可以看到,每大于1分钟,LooperMonitor就会尝试resetPrinter
resetPrinter();
lastCheckPrinterTime = SystemClock.uptimeMillis();
}
return true;
}
关于IdleHandler的实现机制,简单看一下MessageQueue的next方法
当MessageQueue中消息为空时,会执行IdleHandler逻辑,所以IdleHandler是在主线程空闲的时候执行的,通常用于执行时效性不强的逻辑,不影响主线程的运行。
Message next() {
for (;;) {
//消息执行完成
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
boolean keep = false;
try {
//dleHandler是在主线程空闲的时候执行的
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
}
}
销毁
- 清空监听者
- 将原始的Printer重新设置给Looper
- 移除所有IdleHandler
- 置空资源
public synchronized void onRelease() {
if (printer != null) {
synchronized (listeners) {
listeners.clear();
}
looper.setMessageLogging(printer.origin);
removeIdleHandler(looper);
looper = null;
printer = null;
}
}
总结
至此LooperMonitor的全部功能就分析完了,LooperMonitor实现较为简单,但是所起的作用很大,后边进行其他类型tracer的分析几乎都会涉及到LooperMonitor。总结一下它的核心逻辑:
- 通过给Looper设置Printer,监听消息执行前后的回调。
- 内部队列记录当前消息最近的历史消息耗时情况,辅助分析耗时问题。