Android端腾讯性能监控框架Matrix

1丶 TraceCanary

TracePlugin,它继承自plugin,里面包括四个维度FrameTracerFPSTracerEvilMethodTracerStartUpTracer来分析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);
    }
}

TracePlugin分成四个部分mStartUpTracer、mFPSTracer、mFrameTracer和mEvilMethodTracer,它们都继承了BaseTracer; BaseTracer里监听了ApplicationLifeObserver,即每个activity的生命周期和前后台状态的监听;BaseTracer监听着FrameBeat的每一帧刷新前后的时间即doFrame(long lastFrameNanos, long frameNanos); (1)mFrameTracer(屏幕刷新相关)

在doFrame里根据界面绘制的时间差计算,如果超过了正常绘制16.67秒就会在监听里把数据回调出去,这个有两个回调方法doFrameSync和doFrameAsync,对应的是同步调用和异步调用,异步的实现方式利用handler机制,其中getScene是当前的activity或fragment的类名。 

@Override
public void doFrame(final long lastFrameNanos, final long frameNanos) {
    if (!isDrawing) {
        return;
    }
    isDrawing = false;
    final int droppedCount = (int) ((frameNanos - lastFrameNanos) / REFRESH_RATE_MS);
    if (droppedCount > 1) {
        for (final IDoFrameListener listener : mDoFrameListenerList) {
            listener.doFrameSync(lastFrameNanos, frameNanos, getScene(), droppedCount);
            if (null != listener.getHandler()) {
                listener.getHandler().post(new Runnable() {
                    @Override
                    public void run() {
                        listener.getHandler().post(new AsyncDoFrameTask(listener,
                                lastFrameNanos, frameNanos, getScene(), droppedCount));
                    }
                });
            }
        }
    }
}

(2)mEvilMethodTracer

EvilMethodTracer顾名思义就是找到那些邪恶的方法,也就是耗时多的方法。数据大概是这样:

content[{"machine":"HIGH"

,"cpu_app":0

,"mem":7908851712

,"mem_free":4317912

,"detail":"NORMAL"

,"cost":1752

,"usage":"1.26%"

,"scene":"xxx.xxx.xx.xx.MainActivity"

,"stack":""

,"stackKey":""

,"tag":"Trace_EvilMethod"

,"process":"xxx.xxx.xxx"

,"time":1647848753331}]

(3)mStartUpTracer

StartUpTracer是用于分析activity的启动时间的,application启动耗时、SplashActivity(欢迎页)耗时、应用和activity之间的耗时。

(4mFPSTracer

FPSTracer统计的是帧率,统计对应的activity、fragment的掉帧水平。

content[{"machine":"HIGH",
"cpu_app":0,
"mem":7908851712,
"mem_free":4042724,
"scene":"xxx.xxx.xxx.xx.MainActivity",
"dropLevel":{"DROPPED_FROZEN":5,
"DROPPED_HIGH":0,
"DROPPED_MIDDLE":4,
"DROPPED_NORMAL":6,
"DROPPED_BEST":187},
"dropSum":{"DROPPED_FROZEN":485,
"DROPPED_HIGH":0,
"DROPPED_MIDDLE":63,
"DROPPED_NORMAL":21,
"DROPPED_BEST":23},
"fps":15.220011711120605,
"tag":"Trace_FPS","process":"xxx.xxx.xxx",
"time":1647851378435}]

tracePlugin总结

主要分析得失TracePlugin的实现,其中包括了ANR记录、超时函数记录、帧数统计和启动记录。

   // matrix
        DynamicConfigImplDemo dynamicConfig = new DynamicConfigImplDemo();
        boolean fpsEnable = dynamicConfig.isFPSEnable();
        boolean traceEnable = dynamicConfig.isTraceEnable();
        Matrix.Builder maxBuilder = new Matrix.Builder(sApplication);
        maxBuilder.pluginListener(new VenuciaappPluginListener(sContext));
        //trace
        TraceConfig traceConfig = new TraceConfig.Builder()
                .dynamicConfig(dynamicConfig)
                //按照自己需求开启以下监控任务
                .enableFPS(fpsEnable)
                .enableEvilMethodTrace(traceEnable)
                .enableAnrTrace(traceEnable)
                .enableStartup(traceEnable)
                .splashActivities("xxx.xxx.xxx.xx.SplashActivity")
                //debug模式
                .isDebug(true)
                //dev环境
                .isDevEnv(false)
                .build();
        TracePlugin tracePlugin = new TracePlugin(traceConfig);
        maxBuilder.plugin(tracePlugin);
        Matrix.init(maxBuilder.build());
        tracePlugin.start();

2、IOCanary

检测主线程 I/O

  1. 操作线程为主线程
  2. 连续读写耗时超过一定阈值或单次 write\read 耗时超过一定阈值

主要包括文件 I/O 监控和 Closeable Leak 监控两部分。通过使用 IOCanary ,可以快速发现常见的 I/O 问题,提高开发质量

使用

            //io
        IOConfig ioConfig = new IOConfig.Builder()
                .dynamicConfig(dynamicConfig)
                .build();
        IOCanaryPlugin ioCanaryPlugin = new IOCanaryPlugin(ioConfig);
        maxBuilder.plugin(ioCanaryPlugin);
         Application);

(1)write超过阈值检测

 //这里write8000次 512个字节数组
   private fun writeLongSth() {
        try {
            val f = File("/sdcard/a_long.txt")
            if (f.exists()) {
                f.delete()
            }
            val data = ByteArray(512)
            for (i in data.indices) {
                data[i] = 'a'.toByte()
            }
            val fos = FileOutputStream(f)
            for (i in 0 until 10000 * 8) {
                fos.write(data)
            }
            fos.flush()
            fos.close()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }


 //检测日志
 tag[io]type[2];
 key[null];
 content[{"path":"\/sdcard\/a_long.txt",
 "size":40960000,
 "op":80000,
 "buffer":512,
 "cost":586,
 "opType":2,
 "opSize":40960000,
 "thread":"main"
 ,"stack":"android.app.ActivityThread$AndroidOs
 .open(ActivityThread.java:7463)
 \ncom.szlanyou.iov.tencent.io
 .TestIOActivity.writeLongSth(TestIOActivity.kt:173)
xxx.xxx.xxx.tencent.io.TestIOActivity
 .onClick(TestIOActivity.kt:79)\n
 java.lang.reflect.Method.invoke(Native Method)
 androidx.appcompat.app
 .AppCompatViewInflater$DeclaredOnClickListener
 .onClick(AppCompatViewInflater.java:409)
 android.view.View.performClick(View.java:7187)
android.view.View.performClickInternal(View.java:7160)
 android.view.View.access$3500(View.java:824)
 android.view.View$PerformClick.run(View.java:27664)
 android.app.ActivityThread.main(ActivityThread.java:7566)\n",
 "repeat":0,"tag":"io"
 ,"type":2
 ,"process":"xxx.xxx.xxx"
 ,"time":1647934014624}]

(2)file leak检测

 //这里读写数据流时未关导致的内存泄漏
    private fun leakSth() {
        writeSth()
        try {
            val f = File("/sdcard/a.txt")
            val buf = ByteArray(400)
            val fis = FileInputStream(f)
            val count = 0
            while (fis.read(buf) != -1) {
//                MatrixLog.i(TAG, "read %d", ++count);
            }
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }

        //need to trigger gc to detect leak
        //need to trigger gc to detect leak
        Thread {
            Runtime.getRuntime().gc()
            Runtime.getRuntime().runFinalization()
            Runtime.getRuntime().gc()
         }.start()
    }

   private fun writeSth() {
        try {
            val f = File("/sdcard/a.txt")
            if (f.exists()) {
                f.delete()
            }
            val data = ByteArray(4096)
            for (i in data.indices) {
                data[i] = 'a'.toByte()
            }
            val fos = FileOutputStream(f)
            for (i in 0..9) {
                fos.write(data)
            }
            fos.flush()
            fos.close()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }



 //日志检测
 ag[io]type[4];key[java.util.zip.ZipFile.(ZipFile.java:273)
    java.util.zip.ZipFile.(ZipFile.java:187)
    java.util.zip.ZipFile.(ZipFile.java:201)
    miui.content.res.ThemeZipFile$MyZipFile.(ThemeZipFile.java:355)
    miui.content.res.ThemeZipFile.checkUpdate(ThemeZipFile.java:92)
    miui.content.res.ThemeResources.checkUpdate(ThemeResources.java:221)
    miui.content.res.ThemeResources.(ThemeResources.java:216)
    miui.content.res.ThemeResourcesPackage.(ThemeResourcesPackage.java:79)
    miui.content.res.ThemeResourcesPackage.getTopLevelThemeResources(ThemeResourcesPackage.java:69)
    miui.content.res.ThemeResourcesPackage.getThemeResources(ThemeResourcesPackage.java:40)

(3)buffer过小

 // read的时候给定一个小的buffer
   private void smallBuffer() {
        readSth();
    }

   private void readSth() {
        try {
            File f = new File("/sdcard/a_long.txt");
            byte[] buf = new byte[400];
            FileInputStream fis = new FileInputStream(f);
            int count = 0;
            while (fis.read(buf) != -1) {
//                MatrixLog.i(TAG, "read %d", ++count);
            }
            fis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 //检测日志
 tag[io]type[2];
 key[null];
 content[{"path":"\/sdcard\/a_long.txt"
 ,"size":40960000
 ,"op":102401
 ,"buffer":400
 ,"cost":274
 ,"opType":1
 ,"opSize":40960400
 ,"thread":"main"
 ,"stack":"android.app.ActivityThread$AndroidOs
 .open(ActivityThread.java:7463)
 com.szlanyou.iov.tencent.io.
 TestIOActivity.readSth(TestIOActivity.kt:102)
 com.szlanyou.iov.tencent.io.
 TestIOActivity.smallBuffer(TestIOActivity.kt:95)
 com.szlanyou.iov.tencent.io.
 TestIOActivity.onClick(TestIOActivity.kt:88)
 java.lang.reflect.Method.invoke(Native Method)
 androidx.appcompat.app
 .AppCompatViewInflater$DeclaredOnClickListener
 .onClick(AppCompatViewInflater.java:409)
 android.view.View.performClick(View.java:7187)
 android.view.View.performClickInternal(View.java:7160)
 android.view.View.access$3500(View.java:824)
 android.view.View$PerformClick.run(View.java:27664)
 ","repeat":0,"tag":"io","type":2,"process":"com.szlanyou.iov"
 ,"time":1647936449740}]

3、ResourceCanary

ResourceCanary后能实现下面的目标:

  • 自动且较为准确地监测Activity泄漏,发现泄漏之后再触发Dump Hprof而不是根据预先设定的内存占用阈值盲目触发
  • 自动获取泄漏的Activity和冗余Bitmap对象的引用链
  • 能灵活地扩展Hprof的分析逻辑,必要时允许提取Hprof文件人工分析

使用

           //resource
        ResourceConfig resourceConfig = new ResourceConfig.Builder()
                .dynamicConfig(dynamicConfig)
                .setAutoDumpHprofMode(ResourceConfig.DumpMode.MANUAL_DUMP)
                .setManufacture(Build.MANUFACTURER)
                .setManualDumpTargetActivity("xxx.xxx.xxx.ccm.CCMActivity")
                //只有官方demo中才会为true
                .setDetectDebuger(true)
                .build();
        ResourcePlugin resourcePlugin = new ResourcePlugin(resourceConfig);
        maxBuilder.plugin(resourcePlugin);

       // 当activity destroy之后,自动断开从引用的view到gc root之间的路径
        ResourcePlugin.activityLeakFixer(s 

DumpMode:

     public enum DumpMode {
        NO_DUMP, // report only
        AUTO_DUMP, // auto dump hprof
        MANUAL_DUMP, // notify only
        SILENCE_ANALYSE, // dump and analyse hprof when screen off
        FORK_DUMP, // fork dump hprof immediately
        FORK_ANALYSE, // fork dump and analyse hprof immediately
        LAZY_FORK_ANALYZE, // fork dump immediately but analyze hprof until the screen is off
    }

MANUAL_DUMP一定要传activity,不然会报空指针。

内存泄露

        content[{"dump_mode":"MANUAL_DUMP",
        "activity":"xxx.xxx.xxx.ccm.CCMActivity",
        "ref_key":"MATRIX_RESCANARY_REFKEY_com.szlanyou.iov.ccm.
        CCMActivity_16f442b6fb7e4f039d0c54bb5307207b"
        ,"leak_detail":"Leak Reference:android.os.MessageQueue mMessages;
        android.os.Message callback;com.analysys.visual.bind
        .VisualBindManager$a a;
        com.analysys.visual.bind.VisualBindManager mListRootView;
        java.util.ArrayList elementData;array java.lang.Object[] [3];
        com.analysys.ui.RootView view;com.android.internal.policy.DecorView mContentRoot;android.widget.LinearLayout mContext;xxx.xxx.xxx.ccm.CCMActivity instance
        ;"
        ,"cost_millis":"9500"
        ,"tag":"memory"
        ,"process":"xxx.xxx.xxx","time":1647913981637}]

4、SqliteCanary

SQLiteLint在APP运行时进行检测,而且大部分检测算法与数据量无关即不依赖线上的数据状态。只要你触发了某条sql语句的执行,SQLiteLint就会帮助你review这条语句是否写得有问题。而这在开发、测试或者灰度阶段就可以进行。

  1. 收集APP运行时的sql执行信息
    包括执行语句、创建的表信息等。其中表相关信息可以通过pragma命令得到。对于执行语句,有两种情况:
    a) DB框架提供了回调接口。比如微信使用的是WCDB,很容易就可以通过MMDataBase.setSQLiteTrace 注册回调拿到这些信息。
    b) 若使用Android默认的DB框架,SQLiteLint提供了一种无侵入的获取到执行的sql语句及耗时等信息的方式。通过hook的技巧,向SQLite3 C层的api sqlite3_profile方法注册回调,也能拿到分析所需的信息,从而无需开发者额外的打点统计代码。
  2. 预处理
    包括生成对应的sql语法树,生成不带实参的sql,判断是否select*语句等,为后面的分析做准备。预处理和后面的算法调度都在一个单独的处理线程。
  3. 调度具体检测算法执行
    checker就是各种检测算法,也支持扩展。并且检测算法都是以C++实现,方便支持多平台。而调度的时机包括:最近未分析sql语句调度,抽样调度,初始化调度,每条sql语句调度。
  4. 发布问题
    上报问题或者弹框提示。

使用

        //sqlite
        SQLiteLintConfig config = new SQLiteLintConfig(SQLiteLint.SqlExecutionCallbackMode.CUSTOM_NOTIFY);
        SQLiteLintPlugin sqLiteLintPlugin = new SQLiteLintPlugin(config);
        maxBuilder.plugin(sqLiteLintPlugin);
       sqLiteLintPlugin.start();

你可能感兴趣的:(java,android,android,studio)