Android性能优化系列-腾讯matrix-TracePlugin卡顿优化之LooperMonitor源码分析

566 阅读7分钟

这是性能优化系列之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)

先看下方法定义,

  1. 第一个参数表示printer打印的日志,如:
">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what
  1. 第二个参数表示当前message执行的耗时时长,也就是handler的dispatchMessage执行的时长。
  2. 第三个参数是配置进来的,暂时看不出来,继续往下。
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);
                }
            }
        }
    }
}

销毁

  1. 清空监听者
  2. 将原始的Printer重新设置给Looper
  3. 移除所有IdleHandler
  4. 置空资源
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。总结一下它的核心逻辑:

  1. 通过给Looper设置Printer,监听消息执行前后的回调。
  2. 内部队列记录当前消息最近的历史消息耗时情况,辅助分析耗时问题。