Matrix 简介(一)

简介

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接口。类图如下:


Matrix 简介(一)_第1张图片
11.png

其中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 启动耗时

类图如下:


Matrix 简介(一)_第2张图片
12.png

这几个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的子类完成。


Matrix 简介(一)_第3张图片
13.png

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

你可能感兴趣的:(Matrix 简介(一))