Android性能优化系列-腾讯matrix-TracePlugin卡顿优化之ANR监控LooperAnrTracer源码分析
前言
matrix卡顿监控,下面几篇属于基础,可先行查阅,否则今天的内容读起来可能有点困难。
言归正传,今天我们进行LooperAnrTracer的源码分析,LooperAnrTracer是在TracePlugin中进行初始化和调用的,我们直接从它的几个关键方法入手:
- 构造方法
- onStartTrace
- onStopTrace
构造方法
构造方法只是接收了config配置,没有其他逻辑。
public LooperAnrTracer(TraceConfig traceConfig) {
this.traceConfig = traceConfig;
this.isAnrTraceEnable = traceConfig.isAnrTraceEnable();
}
onStartTrace
@Override
public void onAlive() {
super.onAlive();
if (isAnrTraceEnable) {
UIThreadMonitor.getMonitor().addObserver(this);
this.anrHandler = new Handler(MatrixHandlerThread.getDefaultHandler().getLooper());
this.lagHandler = new Handler(MatrixHandlerThread.getDefaultHandler().getLooper());
}
}
添加监听之后,初始化了两个Handler对象,anrHandler和lagHandler,这两个Handler都是和HandlerThread中的looper关联,所以通过它们发送的消息都是在子线程执行。
接下来我们将目光转移到UIThreadMonitor的两个回调方法上。
dispatchBegin
@Override
public void dispatchBegin(long beginNs, long cpuBeginMs, long token) {
super.dispatchBegin(beginNs, cpuBeginMs, token);
//通过AppMethodBeat传入AnrTracer#dispatchBegin做一个标记
anrTask.beginRecord = AppMethodBeat.getInstance().maskIndex("AnrTracer#dispatchBegin");
//保存开始执行的时间
anrTask.token = token;
long cost = (System.nanoTime() - token) / Constants.TIME_MILLIS_TO_NANO;
anrHandler.postDelayed(anrTask, Constants.DEFAULT_ANR - cost);
lagHandler.postDelayed(lagTask, Constants.DEFAULT_NORMAL_LAG - cost);
}
看下两个Task的实现
AnrHandleTask
run方法很长,一段一段来看,首先获取到各个系统参数。
//当前时间
long curTime = SystemClock.uptimeMillis();
//是否在前台
boolean isForeground = isForeground();
// process进程优先级信息
int[] processStat = Utils.getProcessPriority(Process.myPid());
//从AppMethodBeat中拷贝出这5s内所有方法执行的耗时信息
long[] data = AppMethodBeat.getInstance().copyData(beginRecord);
beginRecord.release();
//可见页面
String scene = AppActiveMatrixDelegate.INSTANCE.getVisibleScene();
// memory内存信息
long[] memoryInfo = dumpMemory();
// Thread state 堆栈信息
Thread.State status = Looper.getMainLooper().getThread().getState();
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
String dumpStack;
if (traceConfig.getLooperPrinterStackStyle() == TraceConfig.STACK_STYLE_WHOLE) {
dumpStack = Utils.getWholeStack(stackTrace, "|*\t\t");
} else {
dumpStack = Utils.getStack(stackTrace, "|*\t\t", 12);
}
// frame时间信息
UIThreadMonitor monitor = UIThreadMonitor.getMonitor();
//input事件执行消耗的时长
long inputCost = monitor.getQueueCost(UIThreadMonitor.CALLBACK_INPUT, token);
//animation事件执行消耗的时长
long animationCost = monitor.getQueueCost(UIThreadMonitor.CALLBACK_ANIMATION, token);
//traversal事件执行消耗的时长
long traversalCost = monitor.getQueueCost(UIThreadMonitor.CALLBACK_TRAVERSAL, token);
第二步,对方法信息进行转换。这两个方法我们在其他文章中有分析过。将long数组类型的方法信息解析到LinkedList中,并筛选出耗时最长的一些保留下来,其他过滤掉。
// trace
LinkedList<MethodItem> stack = new LinkedList();
if (data.length > 0) {
//转为LinkedList<MethodItem>
TraceDataUtils.structuredDataToStack(data, stack, true, curTime);
//过滤筛选出符合条件的方法
TraceDataUtils.trimStack(stack, Constants.TARGET_EVIL_METHOD_STACK, new TraceDataUtils.IStructuredDataFilter() {
@Override
public boolean isFilter(long during, int filterCount) {
return during < filterCount * Constants.TIME_UPDATE_CYCLE_MS;
}
@Override
public int getFilterMaxCount() {
return Constants.FILTER_STACK_MAX_COUNT;
}
@Override
public void fallback(List<MethodItem> stack, int size) {
MatrixLog.w(TAG, "[fallback] size:%s targetSize:%s stack:%s", size, Constants.TARGET_EVIL_METHOD_STACK, stack);
Iterator iterator = stack.listIterator(Math.min(size, Constants.TARGET_EVIL_METHOD_STACK));
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
}
});
}
最后一步就是信息的拼装上报了
Issue issue = new Issue();
issue.setKey(token + "");
issue.setTag(SharePluginInfo.TAG_PLUGIN_EVIL_METHOD);
issue.setContent(jsonObject);
plugin.onDetectIssue(issue);
可以看到,虽然这个监控定义为LoopAnrTracer,但实际上在上报问题的时候,还是将它作为慢方法来记录的,它的issue.setTag(SharePluginInfo.TAG_PLUGIN_EVIL_METHOD)设置的值还是“Trace_EvilMethod”。看一个上报的信息示例:
[{
"machine": "BAD",
"cpu_app": 0,
"mem": 1495580672,
"mem_free": 610996,
"detail": "ANR",
"cost": 5000,
"stackKey": "",
"scene": "sample.tencent.matrix.trace.TestTraceMainActivity",
"stack": "",
"threadStack": " \nat android.os.SystemClock:sleep(127)\nat sample.tencent.matrix.trace.TestTraceMainActivity:L(204)\nat sample.tencent.matrix.trace.TestTraceMainActivity:A(150)\nat sample.tencent.matrix.trace.TestTraceMainActivity:testANR(132)\nat java.lang.reflect.Method:invoke(-2)\nat android.view.View$DeclaredOnClickListener:onClick(5629)\nat android.view.View:performClick(6597)\nat android.view.View:performClickInternal(6574)\nat android.view.View:access$3100(778)\nat android.view.View$PerformClick:run(25885)\nat android.os.Handler:handleCallback(873)\nat android.os.Handler:dispatchMessage(99)\nat android.os.Looper:loop(193)\nat android.app.ActivityThread:main(6669)\n",
"processPriority": 10,
"processNice": -10,
"isProcessForeground": true,
"memory": {
"dalvik_heap": 11028,
"native_heap": 21257,
"vm_size": 3337120
},
"tag": "Trace_EvilMethod",
"process": "sample.tencent.matrix",
"time": 1695687215465
}]
LagHandleTask
LagHandleTask的逻辑比较简单,LagHandleTask延时2s执行,也就是说超过2s没有执行完,就会打印堆栈信息, 同样的,tag还是“Trace_EvilMethod”。
String scene = AppActiveMatrixDelegate.INSTANCE.getVisibleScene();
boolean isForeground = isForeground();
try {
TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);
if (null == plugin) {
return;
}
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
String dumpStack = Utils.getWholeStack(stackTrace);
JSONObject jsonObject = new JSONObject();
jsonObject = DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication());
jsonObject.put(SharePluginInfo.ISSUE_STACK_TYPE, Constants.Type.LAG);
jsonObject.put(SharePluginInfo.ISSUE_SCENE, scene);
jsonObject.put(SharePluginInfo.ISSUE_THREAD_STACK, dumpStack);
jsonObject.put(SharePluginInfo.ISSUE_PROCESS_FOREGROUND, isForeground);
Issue issue = new Issue();
issue.setTag(SharePluginInfo.TAG_PLUGIN_EVIL_METHOD);
issue.setContent(jsonObject);
plugin.onDetectIssue(issue);
MatrixLog.e(TAG, "happens lag : %s, scene : %s ", dumpStack, scene);
} catch (JSONException e) {
MatrixLog.e(TAG, "[JSONException error: %s", e);
}
}
示例:
[{ "machine": "BAD", "cpu_app": 0, "mem": 1495580672, "mem_free": 611120, "detail": "NORMAL", "cost": 11256, "usage": "0.03%", "scene": "sample.tencent.matrix.trace.TestTraceMainActivity", "stack": "", "stackKey": "", "tag": "Trace_EvilMethod", "process": "sample.tencent.matrix", "time": 1695687221713}]
dispatchEnd
上边的两个task任务都是延时任务,那么什么情况下会将移除,假如应用正常运行自然不应该该上报anr信息的。dispatchStart是消息执行前的回调,dispatchStart之后消息开始执行,执行完成之后回调dispatchEnd,二者是成对出现,此时来到dispatchEnd,看看他做了什么,第一,释放了AppMethodRecord标记得到的IndexRecord;第二,从消息队列移除了两个task任务。
@Override
public void dispatchEnd(long beginNs, long cpuBeginMs, long endNs, long cpuEndMs, long token, boolean isBelongFrame) {
super.dispatchEnd(beginNs, cpuBeginMs, endNs, cpuEndMs, token, isBelongFrame);
anrTask.getBeginRecord().release();
anrHandler.removeCallbacks(anrTask);
lagHandler.removeCallbacks(lagTask);
}
所以结合dispatchBegin和dispatchEnd两个方法的执行逻辑,我们可以知道,每次消息执行前,matrix会发送两个延时消息到消息队列,执行后移除,假如执行期间发生了卡顿导致2s或5s内没有执行dispatchEnd方法导致延时消息没有被移除,那么就会执行延时消息的任务,上报信息。
onStopTrace
onStopTrace会调用到onDead方法。可以看到,这里做了一些收尾工作。
@Override
public void onDead() {
super.onDead();
if (isAnrTraceEnable) {
UIThreadMonitor.getMonitor().removeObserver(this);
anrTask.getBeginRecord().release();
anrHandler.removeCallbacksAndMessages(null);
lagHandler.removeCallbacksAndMessages(null);
}
}
总结
LooperAnrTracer的逻辑还是很简单的,它主要借助于UIThreadMonitor来实现功能,UIThreadMonitor是对Looper Printer的一个封装,用来实现对主线程消息队列中消息执行前后的一个监听,通过监听消息执行耗时的情况来辅助分析。LooperAnrTracer会在消息执行前发送延时消息,若指定时间内,消息执行完成,则延时消息会被排除,若未执行完成,则说明发生卡顿,此时延时消息被执行,收集系统和应用方法执行耗时信息上报。虽说命名为anrtracer,但严格来说并不是可靠的anr监控,所以上报的tag命名还是用的Trace_EvilMethod。下一篇,我们会分析真正的anr监控。
转载自:https://juejin.cn/post/7282692887649222668