likes
comments
collection
share

Android性能优化系列-腾讯matrix-TracePlugin卡顿优化之ANR监控LooperAnrTracer源码分析

作者站长头像
站长
· 阅读数 96

前言

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
评论
请登录