安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之ANR监控Loper AnrTracer源码分析
作者:访客发布时间:2023-12-13分类:程序开发学习浏览:119
前言
矩阵卡顿监控,下面几篇属于基础,可先行查阅,否则今天的内容读起来可能有点困难。
- 安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之Gradle插件-字节码插桩代码分析
- 安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之AppMethodBeat专项分析
- 安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之Loper监视器源码分析
- 安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之UIThRead监视器源码分析
言归正传,今天我们进行Loper AnrTracer的源码分析、Loper AnrTracer是在TracePlugin中进行初始化和调用的,我们直接从它的几个关键方法入手:
- 构造方法
- 开始跟踪
- OnStopTrace
构造方法
构造方法只是接收了配置配置,没有其他逻辑。
public LooperAnrTracer(TraceConfig traceConfig) {
this.traceConfig = traceConfig;
this.isAnrTraceEnable = traceConfig.isAnrTraceEnable();
}
开始跟踪
On Start跟踪方法启动之后会调用到循环AnrTracer的On Alive方法,我们直接看On Alive。DispatchBegin、dispatchEnd、doFrame,LooperAnrTracer在on Alive中,首先通过UIThRead调用将此对象添加为一个监听者,然后就可以收到三个回调Add观察者用到的是前两个方法。UIThreMonitor的实现原理以及三个回调方法的含义请参考安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之UIThRead监视器源码分析。
@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());
}
}
添加监听之后,初始化了两个处理程序对象、和lagHandler、这两个处理程序都是和处理程序线程中的循环程序关联,所以通过它们发送的消息都是在子线程执行。
接下来我们将目光转移到UIThRead监视器的两个回调方法上。
派单开始
从之前的文章中(安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之UIThRead源码分析)我们分析过,DispatchBegin的时机是主线程消息队列中各Message被执行前的回调, 方法的第一个参数表示的是此方法调用时的纳秒值,第二个参数表示方法调用时当前线程运行的时长,单位为毫秒,第三个参数等同于第一个参数.
然后通过调用应用程序方法吃传入“AnrTracer#DispatchBegin”做一个标记(此逻辑的含义可以查看安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之应用程序方法吃专项分析),将两个Runnable任务和任务和滞后任务延时发送到子线程执行,第一个延时5s、第二个延时2s、也就是说5s和2s之后,两个任务都没有被主动移除的话,它们就会执行。
@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);
}
看下两个任务的实现
AnrHandleTask
运行方法很长,一段一段来看,首先获取到各个系统参数。
//当前时间
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);
AppMethodBeat.getInstance().copyData(beginRecord)其中有一条是从AppMethodBeat中拷贝出这5s内所有方法执行的耗时信息,这是什么意思?应用方法Beat是一段时间内所有方法运行信息的一个记录者,记录着所有方法运行消耗的时长,在DispatchBegin中有调用其方法MaskIndex做标记,其实就是在AppMethod Beat内部的一个数组中记录起点,然后调用Copy Data的时候传入这个起点,就可以将起点和当前节点这个区间内所有的方法信息拷贝出来。详细介绍可以查阅安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之AppMethodBeat专项分析。
第二步,对方法信息进行转换.这两个方法我们在其他文章中有分析过.将Long数组类型的方法信息解析到链接列表中,并筛选出耗时最长的一些保留下来,其他过滤掉。
// 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);
跟踪循环AnrTracer,跟踪issue.setTag(SharePluginInfo.TAG_PLUGIN_EVIL_METHOD)可以看到,虽然这个监控定义为“但实际上在上报问题的时候,还是将它作为慢方法来记录的,它的_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
拉格句柄任务的逻辑比较简单,拉格句柄任务延时2s执行,也就是说超过2s没有执行完,就会打印堆栈信息,同样的,标记还是“跟踪_事件方法”。
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}]
发送端
上边的两个任务任务都是延时任务,那么什么情况下会将移除,假如应用正常运行自然不应该该上报和信息的。DispatchStart是消息执行前的回调,DispatchStart之后消息开始执行,执行完成之后回调DispatchEnd,二者是成对出现,此时来到DispatchEnd,看看他做了什么,第一,释放了AppMethodRecord标记得到的索引Record;第二,从消息队列移除了两个任务任务。
@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);
}
所以结合调度开始和调度End两个方法的执行逻辑,我们可以知道,每次消息执行前,矩阵会发送两个延时消息到消息队列,执行后移除,假如执行期间发生了卡顿导致2s或5s内没有执行调度End方法导致延时消息没有被移除,那么就会执行延时消息的任务,上报信息。
OnStopTrace
On停止跟踪会调用到On Dead方法。可以看到,这里做了一些收尾工作.
@Override
public void onDead() {
super.onDead();
if (isAnrTraceEnable) {
UIThreadMonitor.getMonitor().removeObserver(this);
anrTask.getBeginRecord().release();
anrHandler.removeCallbacksAndMessages(null);
lagHandler.removeCallbacksAndMessages(null);
}
}
总结
Loop AnrTracer的逻辑还是很简单的,它主要借助于UIThRead监视器来实现功能,UIThRead监视器是对循环打印机的一个封装,用来实现对主线程消息队列中消息执行前后的一个监听,通过监听消息执行耗时的情况来辅助分析。Loper AnrTracer会在消息执行前发送延时消息,若指定时间内,消息执行完成,则延时消息会被排除,若未执行完成,则说明发生卡顿,此时延时消息被执行,收集系统和应用方法执行耗时信息上报。虽说命名为追踪器,但严格来说并不是可靠的和监控,所以上报的标记命名还是用的跟踪事件方法。下一篇,我们会分析真正的Anr监控.
- 程序开发学习排行
- 最近发表
-
- Wii官方美版游戏Redump全集!游戏下载索引
- 视觉链接预览最好的WordPress常用插件下载博客插件模块
- 预约日历最好的wordpress常用插件下载博客插件模块
- 测验制作人最好的WordPress常用插件下载博客插件模块
- PubNews Plus|WordPress主题博客主题下载
- 护肤品|wordpress主题博客主题下载
- 肯塔·西拉|wordpress主题博客主题下载
- 酷时间轴(水平和垂直时间轴)最好的wordpress常用插件下载博客插件模块
- 作者头像列表/阻止最好的wordPress常用插件下载博客插件模块
- Elementor Pro Forms最好的WordPress常用插件下载博客插件模块的自动完成字段