Android性能优化系列-腾讯matrix-TracePlugin卡顿优化之EvilMethodTracer源码分析
前言
经过不懈的努力,我们终于将tracer相关的底层基础打牢了,包括插件的字节码插桩功能、用来回溯方法执行情况的AppMethodBeat、用来监听主线程消息队列消息执行情况的LooperMonitor以及在LooperMonitor之上又封装了一层来提供更详细消息运行状态信息的的UIThreadMonitor,有了这些基础,再来看EvilMethodTracer以及后边的几个tracer就非常的简单了。没有看过这些基础文章的读者,建议先从这几篇底层基础开始,否则可能会比较吃力:
- 构造方法
- onStartTrace
- onCloseTrace
构造方法
构造方法逻辑很简单,接收config参数,并从config中拿到一个关键参数evilThresholdMs,这个值表示监控阈值,指的是一个VSYNC(VSync是Vertical Synchronization的简写,它利用vertical sync pulse来保证双缓冲在最佳时间点才进行交换,具体可自行百度)期间消息执行消耗的时长,并非一个方法执行消耗的时长,用户可自定义配置,matrix默认的参数是700毫秒,当超过700毫秒的时候,认为它出现耗时问题。
public EvilMethodTracer(TraceConfig config) {
this.config = config;
//耗时阈值
this.evilThresholdMs = config.getEvilThresholdMs();
//是否开启
this.isEvilMethodTraceEnable = config.isEvilMethodTraceEnable();
}
onStartTrace
- dispatchBegin:message消息分发前
- doFrame:message消息分发完成
- dispatchEnd:也表示message消息分发完成,在doFrame后立即调用
@Override
public void onAlive() {
super.onAlive();
if (isEvilMethodTraceEnable) {
UIThreadMonitor.getMonitor().addObserver(this);
}
}
onCloseTrace
onCloseTrace是基类Tracer的方法,它会调用到EvilMethodTracer的onDead方法,当onDead方法执行时,可以看到,只是通过UIThreadMonitor移除了监听,所以从onStartTrace、onCloseTrace这两个方法的调用上来看,EvilMethodTracer的逻辑基本上完全依赖于UIThreadMonitor的回调来实现。
@Override
public void onDead() {
super.onDead();
if (isEvilMethodTraceEnable) {
UIThreadMonitor.getMonitor().removeObserver(this);
}
}
接下来看一下UIThreadMonitor的三个回调方法。
dispatchBegin
@Override
public void dispatchBegin(long beginNs, long cpuBeginMs, long token) {
super.dispatchBegin(beginNs, cpuBeginMs, token);
indexRecord = AppMethodBeat.getInstance().maskIndex("EvilMethodTracer#dispatchBegin");
}
上边调用mark方法之后得到了一个AppMethodBeat.IndexRecord对象,将其保存在了indexRecord变量中。
AppMethodBeat.IndexRecord indexRecord
doFrame
message消息分发完成。在doFrame方法中,仅仅是将方法传递的三个时间值保存了起来。
@Override
public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {
//input类型消息执行消耗的时间
queueTypeCosts[0] = inputCostNs;
//animation类型消息执行消耗的时间
queueTypeCosts[1] = animationCostNs;
//traversal类型消息执行消耗的时间
queueTypeCosts[2] = traversalCostNs;
}
dispatchEnd
@Override
public void dispatchEnd(long beginNs, long cpuBeginMs, long endNs, long cpuEndMs, long token, boolean isVsyncFrame) {
super.dispatchEnd(beginNs, cpuBeginMs, endNs, cpuEndMs, token, isVsyncFrame);
//endNs - beginNs得到的是从开始到结束消耗的总时长
long dispatchCost = (endNs - beginNs) / Constants.TIME_MILLIS_TO_NANO;
try {
//假如总时长大于设定的阈值,那么认为有卡顿问题产生
if (dispatchCost >= evilThresholdMs) {
long[] data = AppMethodBeat.getInstance().copyData(indexRecord);
long[] queueCosts = new long[3];
System.arraycopy(queueTypeCosts, 0, queueCosts, 0, 3);
String scene = AppActiveMatrixDelegate.INSTANCE.getVisibleScene();
//拿到方法运行信息后,开始进入子线程进行分析
MatrixHandlerThread.getDefaultHandler().post(new AnalyseTask(isForeground(), scene, data, queueCosts, cpuEndMs - cpuBeginMs, dispatchCost, endNs / Constants.TIME_MILLIS_TO_NANO));
}
} finally {
indexRecord.release();
}
}
AnalyseTask
耗时问题发生后,就要进入分析阶段,我们直接去看AnalyseTask的analyse方法。analyse方法在report问题之前,会收集整理一些关键的系统环境信息,如cpu信息、内存信息、调用栈信息、当前所在页面等等,这里最关键的两点,一是当前内存的执行堆栈,二是从AppMethodBeat中收集过来的一个时间段内所有方法执行的耗时信息。这些信息能快速有效的帮助分析人员定位问题的所在。
void analyse() {
//拿到进程信息,nice值、priority值
int[] processStat = Utils.getProcessPriority(Process.myPid());
//计算cpu使用时长占比
String usage = Utils.calculateCpuUsage(cpuCost, cost);
LinkedList<MethodItem> stack = new LinkedList();
if (data.length > 0) {
//解析方法long数组,将方法信息封装到MethodItem集合中
TraceDataUtils.structuredDataToStack(data, stack, true, endMs);
//遍历,找出耗时最长的30个方法,
TraceDataUtils.trimStack(stack, Constants.TARGET_EVIL_METHOD_STACK, new TraceDataUtils.IStructuredDataFilter() {
...
});
}
StringBuilder reportBuilder = new StringBuilder();
StringBuilder logcatBuilder = new StringBuilder();
long stackCost = Math.max(cost, TraceDataUtils.stackToString(stack, reportBuilder, logcatBuilder));
String stackKey = TraceDataUtils.getTreeKey(stack, stackCost);
//拼装信息report
try {
TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);
if (null == plugin) {
return;
}
JSONObject jsonObject = new JSONObject();
jsonObject = DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication());
jsonObject.put(SharePluginInfo.ISSUE_STACK_TYPE, Constants.Type.NORMAL);
jsonObject.put(SharePluginInfo.ISSUE_COST, stackCost);
jsonObject.put(SharePluginInfo.ISSUE_CPU_USAGE, usage);
jsonObject.put(SharePluginInfo.ISSUE_SCENE, scene);
jsonObject.put(SharePluginInfo.ISSUE_TRACE_STACK, reportBuilder.toString());
jsonObject.put(SharePluginInfo.ISSUE_STACK_KEY, stackKey);
Issue issue = new Issue();
issue.setTag(SharePluginInfo.TAG_PLUGIN_EVIL_METHOD);
issue.setContent(jsonObject);
plugin.onDetectIssue(issue);
} catch (JSONException e) {
MatrixLog.e(TAG, "[JSONException error: %s", e);
}
}
report格式示例
看一下report的异常信息, 信息不全,stackKey原本应该有很多的方法耗时信息,但是可能是由于模拟器的缘故,没有相关信息。
[
{
"machine":"BAD",
"cpu_app":0,
"mem":2079719424,
"mem_free":1001052,
"detail":"ANR",
"cost":5000,
"stackKey":"",
"scene":"sample.tencent.matrix.trace.TestTraceMainActivity",
"stack":"",
"threadStack":" android.os.SystemClock:sleep(131) sample.tencent.matrix.trace.TestTraceMainActivity:L(204) sample.tencent.matrix.trace.TestTraceMainActivity:A(150) sample.tencent.matrix.trace.TestTraceMainActivity:testANR(132) java.lang.reflect.Method:invoke(-2) android.view.View$DeclaredOnClickListener:onClick(6263) android.view.View:performClick(7448) android.view.View:performClickInternal(7425) android.view.View:access$3600(810) android.view.View$PerformClick:run(28305) android.os.Handler:handleCallback(938) android.os.Handler:dispatchMessage(99) android.os.Looper:loop(223) android.app.ActivityThread:main(7656)",
"processPriority":10,
"processNice":-10,
"isProcessForeground":true,
"memory":**{
"dalvik_heap":12971,
"native_heap":19082,
"vm_size":13954288
},
"tag":"Trace_EvilMethod",
"process":"sample.tencent.matrix",
"time":1695426899842
}
]
总结
总结EvilMethodTracer的逻辑:EvilMethodTracer依赖主线程的消息机制运行,以主线程一个VSYNC期间消息执行消耗的时长作为触发点,默认超过700毫秒时认为发现了卡顿的存在,此时会借助AppMethodBeat收集的方法信息将期间内所有方法运行的信息汇总,按照方法耗时大小排列,列出消耗时长最大的一些方法,并report,协助开发者进行分析定位卡顿发生的根源。
转载自:https://juejin.cn/post/7281499704220123199