腾讯性能监控框架Matrix源码分析之第一篇
概述
前几天腾讯将一款Android应用性能监控的框架matrix开源了,源码地址在https://github.com/Tencent/matrix,作者是微信终端团队。matrix到底是什么?据官方说法如下:
Matrix 是一款微信研发并日常使用的 APM(Application Performance Manage),当前主要运行在 Android 平台上。 Matrix 的目标是建立统一的应用性能接入框架,通过各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。
Matrix 当前监控范围包括:应用安装包大小,帧率变化,启动耗时,卡顿,慢方法,SQLite 操作优化,文件读写,内存泄漏等等(此段截取自matrix的GitHub介绍)
下面直接看源码:
代码的入口在application的onCreate()里进行初始化的,
Matrix.Builder builder = new Matrix.Builder(this);
```
//省略了一部分构造器创建对象的一段代码,这里仅说明是入口
```
Matrix.init(builder.build());
和leakcanary等库一样在application初始化,Matrix的创建采用了常用的构造器模式,现在进入Matrix内部看看
private static volatile Matrix sInstance;
private final HashSet<Plugin> plugins;//插件集合
private final Application application;
private final PluginListener pluginListener;
private Matrix(Application app, PluginListener listener, HashSet<Plugin> plugins) {
this.application = app;
this.pluginListener = listener;
this.plugins = plugins;
for (Plugin plugin : plugins) {
plugin.init(application, pluginListener);
pluginListener.onInit(plugin);
}
}
public static void setLogIml(MatrixLog.MatrixLogImp imp) {
MatrixLog.setMatrixLogImp(imp);
}
public static boolean isInstalled() {
return sInstance != null;
}
public static Matrix init(Matrix matrix) {
if (matrix == null) {
throw new RuntimeException("Matrix init, Matrix should not be null.");
}
synchronized (Matrix.class) {
if (sInstance == null) {
sInstance = matrix;
} else {
MatrixLog.e(TAG, "Matrix instance is already set. this invoking will be ignored");
}
}
return sInstance;
}
matrix里持有一个插件的集合plugins,使用的是hashSet来保证不出现重复,还有一个plugin状态的监听pluginListener。Matrix采用了单例模式,volatile sInstance保证线程可见行,初始化的时候采用了双重检查,在构造函数里给变量赋值并遍历plugins集合,并逐个调用插件的初始化方法plugin.init()
。
那插件plugin是什么呢?下面是plugin的代码:
public abstract class Plugin implements IPlugin, IssuePublisher.OnIssueDetectListener {
private static final String TAG = "Matrix.Plugin";
public static final int PLUGIN_CREATE = 0x00;
public static final int PLUGIN_INITED = 0x01;
public static final int PLUGIN_STARTED = 0x02;
public static final int PLUGIN_STOPPED = 0x04;
public static final int PLUGIN_DESTROYED = 0x08;
private PluginListener pluginListener;
private Application application;
private boolean isSupported = true;
private int status = PLUGIN_CREATE;
@Override
public void init(Application app, PluginListener listener) {
if (application != null || pluginListener != null) {
throw new RuntimeException("plugin duplicate init, application or plugin listener is not null");
}
status = PLUGIN_INITED;
this.application = app;
this.pluginListener = listener;
}
@Override
public void onDetectIssue(Issue issue) {
pluginListener.onReportIssue(issue);
}
public Application getApplication() {
return application;
}
@Override
public void start() {
//省略部分代码
pluginListener.onStart(this);
}
@Override
public void stop() {
//省略部分代码
pluginListener.onStop(this);
}
@Override
public void destroy() {
//省略部分代码
pluginListener.onDestroy(this);
}
}
plugin它是个抽象类,继承了IPlugin和 IssuePublisher.OnIssueDetectListener,IPlugin包括了五种插件的状态分别是CREATE
、INITED
、STARTED
、STOPPED
和DESTROYED
,当plugin状态发生变化时将回调交给pluginListener来处理。OnIssueDetectListener接口是IssuePublisher类里的内部接口,IssuePublisher具体做了两件事,记录问题和暴露问题,其暴露问题的方法就是空实现然后暴露接口,交给实现OnIssueDetectListener
接口的具体类来处理,Plugin继承了这个OnIssueDetectListener
接口,但它也没自己处理,也是同样交留pluginListener来处理。
第一段小结
- Matrix是个单例,它维护着插件的集合plugins和插件不同状态及报错的处理接口
- pluginListener,这个pluginListener是plugins集合共有的,
- matrix初始化的时候会逐个调用plugin 的init方法。
- 插件Plugin是个抽象类,具体的插件需要实现的,matrix框架里自带的插件有
TracePlugin
、IOCanaryPlugin
、SQLiteLintPlugin
、ResourcePlugin
。
下面会一一查看它们的作用和具体实现
TracePlugin
首先来看TracePlugin,它继承自plugin,里面包括四个维度FrameTracer
、FPSTracer
、 EvilMethodTracer
、StartUpTracer
来分析app的,初始化的方法如下:
@Override
public void init(Application app, PluginListener listener) {
super.init(app, listener);
MatrixLog.i(TAG, "trace plugin init, trace config: %s", mTraceConfig.toString());
//低版本不支持
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
MatrixLog.e(TAG, "[FrameBeat] API is low Build.VERSION_CODES.JELLY_BEAN(16), TracePlugin is not supported");
unSupportPlugin();
return;
}
ApplicationLifeObserver.init(app);
mFrameTracer = new FrameTracer(this);
//开关,可以选择不开
if (mTraceConfig.isMethodTraceEnable()) {
mStartUpTracer = new StartUpTracer(this, mTraceConfig);
}
if (mTraceConfig.isFPSEnable()) {
mFPSTracer = new FPSTracer(this, mTraceConfig);
}
if (mTraceConfig.isMethodTraceEnable()) {
mEvilMethodTracer = new EvilMethodTracer(this, mTraceConfig);
}
}
ApplicationLifeObserver.init(app)是利用了application的ActivityLifecycleCallbacks可以对每个activity的生命周期进行监控做了个观察者模式,另外加了判断分析当前app是在前台还是后台,具体实现方式是记录onActivityResumed和onActivityPaused的生命周期,由于新起的activity的onResume会在底层activity的onPause之后,如果onActivityPaused之后600ms没有执行到onActivityResumed则认为当前处于后台。仔细想想这么做会有误伤,如果有个activity启动特别慢,此时超过600ms则判定已经处于后台了,不过这个影响比较小,因为activity启动之后到resume时就又恢复成正常的前台,即使误判也不影响检测,具体实现可以看源码。
在TracePlugin初始化的时候,分别新建了mStartUpTracer、mFPSTracer、mFrameTracer和mEvilMethodTracer,其中的参数mTraceConfig是个简单的配置类,只是记录了开关就不在这展开了。查看matrix的demo开始检测的入口是tracePlugin.start()里,代码如下:
@Override
public void start() {
super.start();
if (!isSupported()) {
return;
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//保证在主线程调用
FrameBeat.getInstance().onCreate();
}
});
if (null != mFPSTracer) {
mFPSTracer.onCreate();
}
if (null != mEvilMethodTracer) {
mEvilMethodTracer.onCreate();
}
if (null != mFrameTracer) {
mFrameTracer.onCreate();
}
if (null != mStartUpTracer) {
mStartUpTracer.onCreate();
}
}
在onstart时在主线程中调用了FrameBeat.getInstance().onCreate(),这里是做UI分析用的。
目前做UI性能卡顿分析一般有两种方式:
一是利用主线程looper的loop方法在寻找msg.target.dispatchMessage(msg)时的前后会分别打印一段log,可以利用log的内容不同或者log的前后次数记录两次log的时间差,这样就可以大致认为是主线程处理msg的时间,如果时间过长则认为卡顿;
二是利用Choreographer,Choreographer就是一个消息处理器,根据vsync 信号 来计算frame,在doFrame方法里可以收到回调的当前时间,正常绘制两次doFrame的时间差应该是1000/60=16.6666毫秒(每秒60帧),但是遇到卡顿或过度重绘等会导致时间拉长。
这里采用的是第二种方式,其doFrame的实现如下:
@Override
public void doFrame(long frameTimeNanos) {
if (isPause) {
return;
}
if (frameTimeNanos < mLastFrameNanos || mLastFrameNanos <= 0) {
mLastFrameNanos = frameTimeNanos;
if (null != mChoreographer) {
mChoreographer.postFrameCallback(this);
}
return;
}
if (null != mFrameListeners) {
for (IFrameBeatListener listener : mFrameListeners) {
listener.doFrame(mLastFrameNanos, frameTimeNanos);
}
if (null != mChoreographer) {
mChoreographer.postFrameCallback(this);
}
mLastFrameNanos = frameTimeNanos;
}
}
记录两次doFrame的时间,交给mFrameListeners执行回调。
下面会分析mFPSTracer 、mFrameTracer、mFrameTracer和mStartUpTracer 的onCreate方法的具体实现,这四个类都继承了BaseTracer类,因此在分析得前先看下BaseTracer,
public abstract class BaseTracer extends IssuePublisher implements ApplicationLifeObserver.IObserver, IFrameBeatListener, IMethodBeatListener {
private final TracePlugin mPlugin;
private static final MethodBeat sMethodBeat = new MethodBeat();
private static final HashMap<Class<BaseTracer>, BaseTracer> sTracerMap =