简介
Matrix 是一款微信研发并日常使用的 APM (Application Performance Manage) ,当前主要运行在 Android 平台上。Matrix 的目标是建立统一的应用性能接入框架,通过对各种性能监控方案快速集成,对性能监控项的异常数据进行采集和分析,输出相应问题的分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。 开源地址:https://github.com/tencent/matrix
功能
Resource Canary:
1 Activity 泄漏
2 Bitmap 冗余
Trace Canary
1 界面流畅性
2 启动耗时
3 页面切换耗时
4 慢函数
5 卡顿
SQLite Lint:
1 按官方最佳实践自动化检测 SQLite 语句的使用质量
IO Canary:
1 检测文件 IO 问题
2 文件 IO 监控
3 Closeable Leak 监控
接入示例
github上的matrix项目下有sample.
TraceConfig traceConfig = new TraceConfig.Builder()
.dynamicConfig(dynamicConfig)
.enableFPS(fpsEnable)
.enableEvilMethodTrace(traceEnable)
.enableAnrTrace(traceEnable)
.enableStartup(traceEnable)
.splashActivities("sample.tencent.matrix.SplashActivity;")
.isDebug(true)
.isDevEnv(true)
.build();
TracePlugin tracePlugin = (new TracePlugin(traceConfig));
builder.plugin(tracePlugin);
if (matrixEnable) {
//resource
builder.plugin(new ResourcePlugin(new ResourceConfig.Builder()
.dynamicConfig(dynamicConfig)
.setDumpHprof(false)
.setDetectDebuger(true) //only set true when in sample, not in your app
.build()));
ResourcePlugin.activityLeakFixer(this);
//io
IOCanaryPlugin ioCanaryPlugin = new IOCanaryPlugin(new IOConfig.Builder()
.dynamicConfig(dynamicConfig)
.build());
builder.plugin(ioCanaryPlugin);
// prevent api 19 UnsatisfiedLinkError
//sqlite
SQLiteLintConfig config = initSQLiteLintConfig();
SQLiteLintPlugin sqLiteLintPlugin = new SQLiteLintPlugin(config);
builder.plugin(sqLiteLintPlugin);
// thread module is not available now,
// ThreadWatcher threadWatcher = new ThreadWatcher(new ThreadConfig.Builder().dynamicConfig(dynamicConfig).build());
// builder.plugin(threadWatcher);
}
Matrix.init(builder.build());
//start only startup tracer, close other tracer.
tracePlugin.start();
Matrix.with().getPluginByClass(SQLiteLintPlugin.class).start();
不过需要注意的是0.6.1 版本的线程相关模块暂未开源,所以运行时需要将相关diamante注释。注释部分包括app的build gradle中的dependencies。
dependencies {
......
implementation group: "com.tencent.matrix", name: "matrix-resource-canary-common", version: MATRIX_VERSION, changing: true
implementation group: "com.tencent.matrix", name: "matrix-io-canary", version: MATRIX_VERSION, changing: true
// implementation group: "com.tencent.matrix", name: "matrix-thread-canary", version: MATRIX_VERSION, changing: true //未开源,需要注释
implementation group: "com.tencent.matrix", name: "matrix-sqlite-lint-android-sdk", version: MATRIX_VERSION, changing: true
......
}
另外一部分是MatrixApplication的onCreate函数中的部分代码:
@Override
public void onCreate() {
Matrix.init(builder.build());
//start only startup tracer, close other tracer.
tracePlugin.start();
Matrix.with().getPluginByClass(SQLiteLintPlugin.class).start();
// Matrix.with().getPluginByClass(ThreadWatcher.class).start(); // 注释
MatrixLog.i("Matrix.HackCallback", "end:%s", System.currentTimeMillis());
此外如果使用的电脑是windows。则需要将app的build.gradle中的apksignerPath改成批处理。改动如下
matrix {
trace {
enable = true
baseMethodMapFile = "${project.projectDir}/matrixTrace/methodMapping.txt"
blackListFile = "${project.projectDir}/matrixTrace/blackMethodList.txt"
}
removeUnusedResources {
enable true
variant = "debug"
needSign true
shrinkArsc true
// apksignerPath = "${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.getBuildToolsVersion()}/apksigner" // window需要改成批处理文件
apksignerPath = "${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.getBuildToolsVersion()}/apksigner.bat"
unusedResources = project.ext.unusedResourcesSet
ignoreResources = ["R.id.*", "R.bool.*"]
}
}
至此sample项目就可以运行起来了。
初始化
下面看下matrix的init 函数,代码如下:
// 创建builder
Matrix.Builder builder = new Matrix.Builder(this);
// 绑定监听器
builder.patchListener(new TestPluginListener(this));
// 创建plugin
TracePlugin tracePlugin = (new TracePlugin(traceConfig));
// 绑定plugin
builder.plugin(tracePlugin);
// 完成初始化
Matrix.init(builder.build());
最终调用build方法触发了matrix的构造函数。
public Matrix build() {
if (pluginListener == null) {
// 创建一个默认的监听器
pluginListener = new DefaultPluginListener(application);
}
return new Matrix(application, pluginListener, plugins);
}
private Matrix(Application app, PluginListener listener, HashSet plugins) {
this.application = app;
this.pluginListener = listener;
this.plugins = plugins;
AppActiveMatrixDelegate.INSTANCE.init(application);// 1 注册监听
for (Plugin plugin : plugins) {
plugin.init(application, pluginListener);//2 初始化plugin
pluginListener.onInit(plugin);
}
}
首先在AppActiveMatrixDelegate 的init方法中注册了ActivityLifecycleCallbacks和ComponentCallbacks2两个监听,这样就能感知到activity的生命周期和内存紧缺状态。然后遍历matrix的所有插件,并对插件调用init方法进行初始化,最终通知pluginListener的init方法。其中PluginListener的代码如下:
public interface PluginListener {
void onInit(Plugin plugin);
void onStart(Plugin plugin);
void onStop(Plugin plugin);
void onDestroy(Plugin plugin);
void onReportIssue(Issue issue);
}
PluginListener 能获取到plugin的状态,也能收到issue。在sample里,是在收到issue的时候弹出一个IssuesList展示issue的具体信息。默认的DefaultPluginListener没有对issue进行处理,只是打印日志。实际上,我们接入matrix,需要定义自己的PluginListener ,对issue进行进一步的处理,比如序列化到本地或者压缩或者上传服务端等。自己实现的onReportIssue方法将决定我们对issue的处理。
类图
从sample的接入中看到有TracePlugin,IOCanaryPlugin,ResourcePlugin和SQLiteLintPlugin四个pugin. 这四个plugin都实现了plugin接口。类图如下:
其中IPlugin接口如下:
public interface IPlugin {
Application getApplication();
void init(Application application, PluginListener pluginListener);
void start();
void stop();
void destroy();
String getTag();
void onForeground(boolean isForeground);
}
主要定义了插件的生命周期,Plugin提供了默认的实现。
TracePlugin
它是trace管理器,期内部定义了四个trace。
- AnrTracer ANR监测
- EvilMethodTracer 耗时函数监测
- FrameTracer 帧率监测
- StartupTracer 启动耗时
类图如下:
这几个trace都继承自Trace.这是抽象类,但不含抽象方法。已经对继承LooperObserver和ITracer来的方法做了默认实现。我们先看下Trace继承的父类和实现的接口。
1.LooperObserver
它是个抽象类,内部定义了三个重要的方法dispatchBegin、doFrame,dispatchEnd。但都是空实现,这个三个方法和监听主线程Handler的消息处理有关。当主线程开始处理一条消息之前那会回调dispatchBegin,消息处理结束会调用doFrame,然后再调用dispatchEnd。之所以这么做是因为对于卡顿的检测通常有两种方式:
(1) 监听主线程Handler的消息处理,通过给looper设置一个logger对象。系统looper分发处理前后会通过logger对象打印日志。这样looger就可以很方便的拿到消息执行的前后时间点,根据二者的事件差可以做很多卡顿的分析,比如blockCanary就是采用这种方式检测的卡顿。
(2) 通过Choreographer开放API ,上层可设置FrameCallback监听,从而获得每一帧绘制完成的onFrame回调。常用的帧率检测工具就是通过分析两帧之间的事件差完成FPS的计算。
2 ITracer
它是一个接口,继承了IAppForeground接口,因此其实现类必须实现四个四个抽象方法:onForeground,isAlive,onStartTrace,onCloseTrace。第一个方法是监听activity的前后台状态,因此trace能感知activity前后台状态变化,可以用来做activity的启动分析。另外三个方法是用来描述trace的生命周期,由TracePlugin来统一管理。而Trace中这四个防范都是空实现,具体实现都有trace的子类完成。
3 FrameTracer
FrameTracer 重写doFrame,并将时间戳,掉帧情况,页面名称等信息发送给IDoFrameListener。
private void notifyListener(final String visibleScene, final long frameCostMs) {
long start = System.currentTimeMillis();
try {
synchronized (listeners) {
for (final IDoFrameListener listener : listeners) {
final int dropFrame = (int) (frameCostMs / frameIntervalMs);
listener.doFrameSync(visibleScene, frameCostMs, dropFrame);
if (null != listener.getHandler()) {
listener.getHandler().post(new Runnable() {
@Override
public void run() {
listener.doFrameAsync(visibleScene, frameCostMs, dropFrame);
}
});
}
}
}
} finally {
}
}
4 EvilMethodTracer
FrameTracer 用来监听函数的执行时间,待补充完善
5 AnrTracer
AnrTracer主要用来判断anr的发生,原理是让handler中延迟发送一个runnable,然后过一段时间尝试取消,如果未能取消,说明执行时间超过了等待时间,可以认为发生了anr.如果取消成功,说明执行的时间小于等待时间。
@Override
public void dispatchBegin(long beginMs, long cpuBeginMs, long token) {
super.dispatchBegin(beginMs, cpuBeginMs, token);
anrTask = new AnrHandleTask(AppMethodBeat.getInstance().maskIndex("AnrTracer#dispatchBegin"), token);
// 延迟发送,延迟的时间为Constants.DEFAULT_ANR。这个即为判断anr的阈值
anrHandler.postDelayed(anrTask, Constants.DEFAULT_ANR - (SystemClock.uptimeMillis() - token));
}
@Override
public void dispatchEnd(long beginMs, long cpuBeginMs, long endMs, long cpuEndMs, long token, boolean isBelongFrame) {
super.dispatchEnd(beginMs, cpuBeginMs, endMs, cpuEndMs, token, isBelongFrame);
if (null != anrTask) {
anrTask.getBeginRecord().release();
anrHandler.removeCallbacks(anrTask);// 取消判断anr的task
}
}
6 StartupTracer
StartupTracer用来判断启动的时间,包括冷启动和热启动。StartupTracer实现了ActivityLifecycleCallbacks的相关方法,主要通过生命周期回调的方式实现启动时间的计算。不过启动事件是通过hook系统handler的方式实现的。
public static void hackSysHandlerCallback() {
try {
sApplicationCreateBeginTime = SystemClock.uptimeMillis();
sApplicationCreateBeginMethodIndex = AppMethodBeat.getInstance().maskIndex("ApplicationCreateBeginMethodIndex");
Class> forName = Class.forName("android.app.ActivityThread");
Field field = forName.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object activityThreadValue = field.get(forName);
Field mH = forName.getDeclaredField("mH");
mH.setAccessible(true);
Object handler = mH.get(activityThreadValue);
Class> handlerClass = handler.getClass().getSuperclass();
Field callbackField = handlerClass.getDeclaredField("mCallback");
callbackField.setAccessible(true);
Handler.Callback originalCallback = (Handler.Callback) callbackField.get(handler);
HackCallback callback = new HackCallback(originalCallback);
callbackField.set(handler, callback);
MatrixLog.i(TAG, "hook system handler completed. start:%s SDK_INT:%s", sApplicationCreateBeginTime, Build.VERSION.SDK_INT);
} catch (Exception e) {
MatrixLog.e(TAG, "hook system handler err! %s", e.getCause().toString());
}
}
主要是将mH对象内部原有的Handler.Callback替换成自定义的HackCallback。
@Override
public boolean handleMessage(Message msg) {
if (!AppMethodBeat.isRealTrace()) {
return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
}
boolean isLaunchActivity = isLaunchActivity(msg);
if (hasPrint > 0) {
MatrixLog.i(TAG, "[handleMessage] msg.what:%s begin:%s isLaunchActivity:%s", msg.what, SystemClock.uptimeMillis(), isLaunchActivity);
hasPrint--;
}
if (isLaunchActivity) {
ActivityThreadHacker.sLastLaunchActivityTime = SystemClock.uptimeMillis();
ActivityThreadHacker.sLastLaunchActivityMethodIndex = AppMethodBeat.getInstance().maskIndex("LastLaunchActivityMethodIndex");
}
if (!isCreated) {
if (isLaunchActivity || msg.what == CREATE_SERVICE || msg.what == RECEIVER) { // todo for provider
ActivityThreadHacker.sApplicationCreateEndTime = SystemClock.uptimeMillis();
ActivityThreadHacker.sApplicationCreateScene = msg.what;
isCreated = true;
}
}
return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
}
参考文献
感谢微信的开源和先行者的无私分享
matix官方介绍
Matrix系列文章(一) 卡顿分析工具之Trace Canary
(4.2.49)微信APM:Matrix源码浅析
深入了解APM讲义V3