TraceCanary分为帧率监控、慢方法监控、ANR监控、启动耗时、主线程优先级检测、IdleHandler耗时检测这6个功能。
基本使用 文档TraceCanary 文档
Matrix GitHub
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();
}
}
结果:
字段含义:
sceneTrace_FPS
dropLevel帧率对应的场景
dropSum衡量帧率掉帧的水平(次数)
fps总共掉帧的总时长
慢方法监控(EvilMethodTracer)帧率
通过监控主线程中每个Message执行的起止时间,如果时间差超过一定的阈值,就认为发生了慢函数调用。此时可以通过AppMethodBeat中的数据,分析出这段时间内函数执行的堆栈信息,以及每个函数执行的耗时。这样,慢函数无所遁形了。
结果:
字段含义:
detail(具体的耗时场景)Trace_EvilMethod
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,代表启动第一个界面初始化过程的堆栈。
coststack耗时
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 的TestTraceMainActivity类
public void testSignalANR(final View view) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果:
字段含义:
detail(具体的耗时场景)Trace_EvilMethod
ANR:anr超时场景
其他字段和慢方法监控字段一样。
在methodMapping.txt 文件中对应结果如下:
启动过程分析流程图:
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就是冷启动的总耗时。
结果:
字段含义:
application_createTrace_StartUp
first_activity_create应用启动的耗时
startup_durationactivity 启动耗时
is_warm_start_up启动总耗时
application_create_scene是否是软启动:true(是) false(不是)
100:代表activity拉起的。114:代表service拉起的。113:代表receiver拉起的。-100:代表未知的
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)