性能检测工具:Matrix-TraceCanary 入门

简介

TraceCanary分为帧率监控、慢方法监控、ANR监控、启动耗时、主线程优先级检测、IdleHandler耗时检测这6个功能。

基本使用

文档

TraceCanary 文档
Matrix GitHub

用法

1. 项目依赖
  • 在项目根目录下的 gradle.properties中配置要依赖的Matrix版本号
MATRIX_VERSION=2.0.5
  • 在项目根目录下的build.gradle文件添加Matrix依赖
dependencies {
      classpath ("com.tencent.matrix:matrix-gradle-plugin:${MATRIX_VERSION}") { changing = true }
}
  • 在 app/build.gradle 文件中添加 Matrix 各模块的依赖
apply plugin: 'com.tencent.matrix-plugin'

matrix {
    trace {
        enable = true	//if you don't want to use trace canary, set false
        baseMethodMapFile = "${project.buildDir}/matrix_output/Debug.methodmap"
        blackListFile = "${project.projectDir}/matrixTrace/blackMethodList.txt"
    }
}


dependencies {
    implementation group: "com.tencent.matrix", name: "matrix-android-lib", version: MATRIX_VERSION, changing: true
    implementation group: "com.tencent.matrix", name: "matrix-android-commons", version: MATRIX_VERSION, changing: true
    implementation group: "com.tencent.matrix", name: "matrix-trace-canary", version: MATRIX_VERSION, changing: true
}
2. 必要代码 (参考 Matrix中sample-android示例代码)
  • 实现PluginListener,接收Matrix处理后的数据(参考TestPluginListener)
public class PluginListener extends DefaultPluginListener {
    public static final String TAG = "Matrix.PluginListener";
    public SoftReference<Context> softReference;
    private final Handler mHandler = new Handler(Looper.getMainLooper());
    public PluginListener(Context context) {
        super(context);
        softReference = new SoftReference<>(context);
    }

    @Override
    public void onReportIssue(Issue issue) {
        super.onReportIssue(issue);
        MatrixLog.e(TAG, issue.toString());
        IssuesMap.put(IssueFilter.getCurrentFilter(), issue);
        jumpToIssueActivity();
    }
    
    public void jumpToIssueActivity() {
        Context context = softReference.get();
        Intent intent = new Intent(context, IssuesListActivity.class);

        if (context instanceof Application) {
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

        context.startActivity(intent);
    }
}

TraceCanary项目本身是没有可视化页面,onReportIssue()方法会输出解析后数据。如果要想可视化效果,可以直接用sample-android中的IssuesListActivity类来集成到自己项目中。同时,还需要sample-android中issue包下IssueFilter、IssuesMap、ParseIssueUtil这些收集和解析数据功能的工具类。

  • 实现动态配置接口,可修改Matrix内部参数(参考DynamicConfigImplDemo类)
public class DynamicConfigImpl implements IDynamicConfig {
    private static final String TAG = "Matrix.DynamicConfigImplDemo";

    public DynamicConfigImpl() {

    }

    public boolean isFPSEnable() {
        return true;
    }

    public boolean isTraceEnable() {
        return true;
    }

    public boolean isSignalAnrTraceEnable() {
        return true;
    }

    public boolean isMatrixEnable() {
        return true;
    }

    @Override
    public String get(String key, String defStr) {
        //TODO here return default value which is inside sdk, you can change it as you wish. matrix-sdk-key in class MatrixEnum.

        // for Activity leak detect
        if ((ExptEnum.clicfg_matrix_resource_detect_interval_millis.name().equals(key) || ExptEnum.clicfg_matrix_resource_detect_interval_millis_bg.name().equals(key))) {
            Log.d("DynamicConfig", "Matrix.ActivityRefWatcher: clicfg_matrix_resource_detect_interval_millis 10s");
            return String.valueOf(TimeUnit.SECONDS.toMillis(5));
        }

        if (ExptEnum.clicfg_matrix_resource_max_detect_times.name().equals(key)) {
            Log.d("DynamicConfig", "Matrix.ActivityRefWatcher: clicfg_matrix_resource_max_detect_times 5");
            return String.valueOf(3);
        }

        return defStr;
    }


    @Override
    public int get(String key, int defInt) {
        //TODO here return default value which is inside sdk, you can change it as you wish. matrix-sdk-key in class MatrixEnum.

        if (MatrixEnum.clicfg_matrix_resource_max_detect_times.name().equals(key)) {
            MatrixLog.i(TAG, "key:" + key + ", before change:" + defInt + ", after change, value:" + 2);
            return 2;//new value
        }

        if (MatrixEnum.clicfg_matrix_trace_fps_report_threshold.name().equals(key)) {
            return 10000;
        }

        if (MatrixEnum.clicfg_matrix_trace_fps_time_slice.name().equals(key)) {
            return 12000;
        }

        return defInt;

    }

    @Override
    public long get(String key, long defLong) {
        //TODO here return default value which is inside sdk, you can change it as you wish. matrix-sdk-key in class MatrixEnum.
        if (MatrixEnum.clicfg_matrix_trace_fps_report_threshold.name().equals(key)) {
            return 10000L;
        }

        if (MatrixEnum.clicfg_matrix_resource_detect_interval_millis.name().equals(key)) {
            MatrixLog.i(TAG, key + ", before change:" + defLong + ", after change, value:" + 2000);
            return 2000;
        }

        return defLong;
    }


    @Override
    public boolean get(String key, boolean defBool) {
        //TODO here return default value which is inside sdk, you can change it as you wish. matrix-sdk-key in class MatrixEnum.

        return defBool;
    }

    @Override
    public float get(String key, float defFloat) {
        //TODO here return default value which is inside sdk, you can change it as you wish. matrix-sdk-key in class MatrixEnum.

        return defFloat;
    }

}
  • 在Application的继承类中,对Matrix进行初始化(参考MatrixApplication)
public class MyApplication extends Application {
    
     @Override
    public void onCreate() {
        super.onCreate();
        
        // Switch.
        DynamicConfigImpl dynamicConfig = new DynamicConfigImpl();
        
        Matrix.Builder builder = new Matrix.Builder(this);
        
        TracePlugin tracePlugin = configureTracePlugin(dynamicConfig);
        builder.plugin(tracePlugin);
        
        Matrix.init(builder.build());
        tracePlugin.start();
    }
    
    private TracePlugin configureTracePlugin(DynamicConfigImpl dynamicConfig) {

        boolean fpsEnable = dynamicConfig.isFPSEnable();
        boolean traceEnable = dynamicConfig.isTraceEnable();
        boolean signalAnrTraceEnable = dynamicConfig.isSignalAnrTraceEnable();

        File traceFileDir = new File(getApplicationContext().getFilesDir(), "matrix_trace");
        if (!traceFileDir.exists()) {
            if (traceFileDir.mkdirs()) {
                MatrixLog.e(TAG, "failed to create traceFileDir");
            }
        }
        
        File anrTraceFile = new File(traceFileDir, "anr_trace"); 
        File printTraceFile = new File(traceFileDir, "print_trace");
        
        TraceConfig traceConfig = new TraceConfig.Builder()
                .dynamicConfig(dynamicConfig)
                .enableFPS(fpsEnable)
                .enableEvilMethodTrace(traceEnable)
                .enableAnrTrace(traceEnable)
                .enableStartup(traceEnable)
                .enableIdleHandlerTrace(traceEnable)                   
                .enableMainThreadPriorityTrace(true)                    
                .enableSignalAnrTrace(signalAnrTraceEnable)             
                .anrTracePath(anrTraceFile.getAbsolutePath())
                .printTracePath(printTraceFile.getAbsolutePath())
                // 换成自己项目启动页
                .splashActivities("sample.tencent.matrix.SplashActivity;")
                .isDebug(true)
                .isDevEnv(false)
                .build();
        
        return new TracePlugin(traceConfig);        
    }
}

至此,Matrix就已成功集成到你的项目中,并且开始收集和分析性能相关异常数据。

sample-android效果:
在这里插入图片描述

原理

TraceCanary 采用ASM插桩方案。插桩打点操作的类就是AppMethodBeat,该类会在编译期对函数的出入进行插桩i(methodId)/o(methodId),并在Activity#onWindowFocusChanged方法中插入at方法,供后面各种tracer使用。插桩的核心代码在插件中的MethodTracer。

插桩之外,我们还需要监控主线程MessageQueue(LooperMonitor)以及Choreographer(UIThreadMonitor),从里面抽象出一个可以通知Message开始执行、执行完成、Choreographer开始渲染的接口(LooperObserver)。基于这个接口,我们可以开始监控的实现。

FrameTracer(帧率监控)

在UIThreadMonitor中会通过LooperMonitor监听所有主线程的Message的执行,同时会向Choreographer#callbackQueues中插入一个回调来监听CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL这三种事务的执行耗时。

这样当Choreographer#FrameHandler开始执行的vsync时,UIThreadMonitor就可以捕获到vsync执行的起止时间,以及doFrame时各个部分的耗时。然后可以通过一系列计算来计算出帧率。实际上,只需要知道渲染每一帧的起止时间就可以算出帧率了。

// 具体代码见:sample-android 的TestFpsActivity类,下面给出关键代码

public class TestFpsActivity extends Activity {
    // 帧率变化监听
    private IDoFrameListener mDoFrameListener = new IDoFrameListener(....){
    
    public void doFrameAsync(String focusedActivity, long startNs, long endNs, int dropFrame, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {
            super.doFrameAsync(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
        ...
    }
        
    };
    
     protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        
        IssueFilter.setCurrentFilter(IssueFilter.ISSUE_TRACE);
        // FrameTracer开始跟踪
        Matrix.with().getPluginByClass(TracePlugin.class).getFrameTracer().onStartTrace();
        // 添加监听
        Matrix.with().getPluginByClass(TracePlugin.class).getFrameTracer().addListener(mDoFrameListener);
        
        ...
     } 
     
     protected void onDestroy() {
        super.onDestroy();
        // 移除监听
        Matrix.with().getPluginByClass(TracePlugin.class).getFrameTracer().removeListener(mDoFrameListener);
        // FrameTracer结束跟踪
        Matrix.with().getPluginByClass(TracePlugin.class).getFrameTracer().onCloseTrace();
    }
}

结果:
在这里插入图片描述
字段含义:

  • tag

Trace_FPS

  • scene

帧率对应的场景

  • dropLevel

衡量帧率掉帧的水平(次数)

  • dropSum

总共掉帧的总时长

  • fps

帧率

慢方法监控(EvilMethodTracer)

通过监控主线程中每个Message执行的起止时间,如果时间差超过一定的阈值,就认为发生了慢函数调用。此时可以通过AppMethodBeat中的数据,分析出这段时间内函数执行的堆栈信息,以及每个函数执行的耗时。这样,慢函数无所遁形了。

在这里插入图片描述
结果:
在这里插入图片描述
字段含义:

  • tag

Trace_EvilMethod

  • detail(具体的耗时场景)

NORMAL:代表普通慢函数场景。 ENTER:代表Activity进入场景。 ANR:anr超时场景。
FULL:代表满buffer场景。 STARTUP:启动耗时场景。

如果detail == ENTER,会增加viewInfo字段。包含3个属性:viewDeep:view的深度;viewCount :view的数量;activity :activity的name。

如果detail == STARTUP,会增加subType字段(默认值-1)。如果subType=1,代表application初始化过程的堆栈;如果subType=2,代表启动第一个界面初始化过程的堆栈。

  • cost

耗时

  • stack

堆栈

  • stackKey

客户端提取的 key,用来标识issue的唯一性(方法ID)

打开app/build/outputs/mapping/debug/methodMapping.txt文件。从上图可知stackKey是118也就是方法id。
在这里插入图片描述
在这里插入图片描述

由上图可知,stackKey=118,对应就是TestEnterActivity类的onCreate 方法

ANR监控(AnrTracer)

在主线程中一般认为超过5s就会发生ANR。通过分析AppMethodBeat中的数据得到函数执行的堆栈以及耗时。

具体代码见:sample-android 的TestTraceMainActivitypublic void testSignalANR(final View view) {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 }

结果:
在这里插入图片描述
字段含义:

  • tag

Trace_EvilMethod

  • detail(具体的耗时场景)

ANR:anr超时场景

其他字段和慢方法监控字段一样。

在methodMapping.txt 文件中对应结果如下:
在这里插入图片描述

StartupTracer(启动耗时)

启动过程分析流程图:

firstMethod.i       LAUNCH_ACTIVITY   onWindowFocusChange   LAUNCH_ACTIVITY    onWindowFocusChange
 ^                         ^                   ^                     ^                  ^
 |                         |                   |                     |                  |
 t0                        t1                  t2                                       t3
 |---------app---------|---|---firstActivity---|---------...---------|---careActivity---|
 |<--applicationCost-->|
 |<--------------firstScreenCost-------------->|
 |<---------------------------------------coldCost------------------------------------->|
 .                         |<-----warmCost---->|

AppMethodBeat.i第一次发生时,可以认为是Application创建的时间(t0);第一次Activity的启动或者Service、Receiver的创建认为是Application创建的结束时间(t1)。这两者的时间差即为Application创建耗时。

第一个Activity的onWindowFocusChange发生时,即为时间t2,t2-t0就是首屏启动耗时。第二个Activity(一般是真正的MainActivity)的onWindowFocusChange发生时,即为时间t3,t3-t0就是冷启动的总耗时。

结果:
在这里插入图片描述
字段含义:

  • tag

Trace_StartUp

  • application_create

应用启动的耗时

  • first_activity_create

activity 启动耗时

  • startup_duration

启动总耗时

  • is_warm_start_up

是否是软启动:true(是) false(不是)

  • application_create_scene

100:代表activity拉起的。114:代表service拉起的。113:代表receiver拉起的。-100:代表未知的

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值