美文网首页APM框架分析
Matrix 简介(一)

Matrix 简介(一)

作者: David_zhou | 来源:发表于2020-04-15 10:42 被阅读0次

    简介

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


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

    类图如下:


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


    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 简介(一)

        本文链接:https://www.haomeiwen.com/subject/vmbqvhtx.html