联系我们
简单又实用的WordPress网站制作教学
当前位置:网站首页 > 程序开发学习 > 正文

安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之ANR监控Loper AnrTracer源码分析

作者:访客发布时间:2023-12-13分类:程序开发学习浏览:73


导读:前言矩阵卡顿监控,下面几篇属于基础,可先行查阅,否则今天的内容读起来可能有点困难。安卓性能优化系列-腾讯矩阵-TracePlugin卡顿优化之Gradle插件-字节码插桩代码分析安...

前言

矩阵卡顿监控,下面几篇属于基础,可先行查阅,否则今天的内容读起来可能有点困难。

  • 安卓性能优化系列-腾讯矩阵-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监控.


标签:腾讯源码性能系列安卓系统ANR


程序开发学习排行
最近发表
网站分类
标签列表