likes
comments
collection
share

Android性能优化系列-腾讯matrix-TracePlugin卡顿优化之EvilMethodTracer源码分析

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

前言

经过不懈的努力,我们终于将tracer相关的底层基础打牢了,包括插件的字节码插桩功能、用来回溯方法执行情况的AppMethodBeat、用来监听主线程消息队列消息执行情况的LooperMonitor以及在LooperMonitor之上又封装了一层来提供更详细消息运行状态信息的的UIThreadMonitor,有了这些基础,再来看EvilMethodTracer以及后边的几个tracer就非常的简单了。没有看过这些基础文章的读者,建议先从这几篇底层基础开始,否则可能会比较吃力:

  1. 构造方法
  2. onStartTrace
  3. 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
评论
请登录