性能优化之matrix学习-Trace Canary

1,824 阅读14分钟

Trace Canary通过字节码插桩的方式在编译期预埋了方法进入、方法退出的埋点。运行期,慢函数检测、FPS检测、卡顿检测、启动检测使用这些埋点信息排查具体哪个函数导致的异常。

插桩方案确实比MessageQueue方案、Choreographer方案更优,可以准确的获取各个函数的执行耗时,还可以准确的获取当前执行的堆栈信息。

其原理是通过代理编译期间的任务 transformClassesWithDexTask,将全局 class 文件作为输入,利用 ASM 工具对所有 class 文件进行扫描及插桩,插桩的意思是在每一个方法的开头处插入 AppMethodBeat.i 方法,在方法的结尾处插入 AppMethodBeat.o 方法,并记录时间戳,这样就能知道该方法的执行耗时。

插桩过程有几个关键点:

  • 选择在编译任务执行时插桩,是因为 proguard 操作是在该任务之前就完成的,意味着插桩时的 class 文件已经被混淆过的。而选择 proguard 之后去插桩,是因为如果提前插桩会造成部分方法不符合内联规则,没法在 proguard 时进行优化,最终导致程序方法数无法减少,从而引发方法数过大问题

  • 为了减少插桩量及性能损耗,通过遍历 class 方法指令集,判断扫描的函数是否只含有 PUT/READ FIELD 等简单的指令,来过滤一些默认或匿名构造函数,以及 get/set 等简单不耗时函数。

  • 针对界面启动耗时,因为要统计从 Activity#onCreate 到 Activity#onWindowFocusChange 间的耗时,所以在插桩过程中需要收集应用内所有 Activity 的实现类,并覆盖 onWindowFocusChange 函数进行打点。

  • 为了方便及高效记录函数执行过程,Matrix 为每个插桩的函数分配一个独立 ID,在插桩过程中,记录插桩的函数签名及分配的 ID,在插桩完成后输出一份 mapping,作为数据上报后的解析支持。为了优化内存使用,method id 及时间戳是通过一个 long 数组记录的,格式如下:

image.png

  • 堆栈聚类问题: 如果将收集的原始数据进行上报,数据量很大而且后台很难聚类有问题的堆栈,所以在上报之前需要对采集的数据进行简单的整合及裁剪,并分析出一个能代表卡顿堆栈的 key,方便后台聚合。具体的方法是通过遍历采集的 buffer ,相邻 i 与 o 为一次完整函数执行,计算出一个调用树及每个函数执行耗时,并对每一级中的一些相同执行函数做聚合,最后通过一个简单策略,分析出主要耗时的那一级函数,作为代表卡顿堆栈的key。

image.png

插桩打点操作的类就是AppMethodBeat,该类会在编译期对函数的出入进行插桩i(methodId)/o(methodId),并在Activity#onWindowFocusChanged方法中插入at方法,供后面各种tracer使用。插桩的核心代码在插件中的MethodTracer。

插桩之外,我们还需要监控主线程MessageQueue(LooperMonitor)以及Choreographer(UIThreadMonitor),从里面抽象出一个可以通知Message开始执行、执行完成、Choreographer开始渲染的接口(LooperObserver)。基于这个接口,我们可以开始监控的实现。

  1. 帧率监控FrameTracer 在UIThreadMonitor中会通过LooperMonitor监听所有主线程的Message的执行,同时会向Choreographer#callbackQueues中插入一个回调来监听CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL这三种事务的执行耗时。

这样当Choreographer#FrameHandler开始执行的vsync时,UIThreadMonitor就可以捕获到vsync执行的起止时间,以及doFrame时各个部分的耗时。然后可以通过一系列计算来计算出帧率。实际上,只需要知道渲染每一帧的起止时间就可以算出帧率了。

  1. 慢函数监控EvilMethodTracer 通过监控主线程中每个Message执行的起止时间,如果时间差超过一定的阈值,就认为发生了慢函数调用。此时可以通过AppMethodBeat中的数据,分析出这段时间内函数执行的堆栈信息,以及每个函数执行的耗时。这样,慢函数无所遁形了。

  2. ANR监控AnrTracer ANR的监控更加简单了,在主线程中一般认为超过5s就会发生ANR。所以在Message开始执行时,抛出一个5s后爆炸的炸弹,在Message执行完毕之后remove。若这颗炸弹最终还是爆炸了,那就说明发生了ANR。此时还是通过分析AppMethodBeat中的数据得到函数执行的堆栈以及耗时。

  3. 启动耗时StartUpTracer 启动耗时我们先要hook一下ActivityThread.mH.mCallback,获取handleMessage执行的Message,然后在AppMethodBeat的帮助下也很简单。

AppMethodBeat.i第一次发生时,可以认为是Application创建的时间(t0);第一次Activity的启动或者Service、Receiver的创建认为是Application创建的结束时间(t1)。这两者的时间差即为Application创建耗时。 第一个Activity的onWindowFocusChange发生时,即为时间t2,t2-t0就是首屏启动耗时。 第二个Activity(一般是真正的MainActivity)的onWindowFocusChange发生时,即为时间t3,t3-t0就是冷启动的总耗时。

 firstMethod.i       LAUNCH_ACTIVITY   onWindowFocusChange   LAUNCH_ACTIVITY    onWindowFocusChange
 ^                         ^                   ^                     ^                  ^
 |                         |                   |                     |                  |
 t0                        t1                  t2                                       t3
 |---------app---------|---|---firstActivity---|---------...---------|---careActivity---|
 |<--applicationCost-->|
 |<--------------firstScreenCost-------------->|
 |<---------------------------------------coldCost------------------------------------->|
 .                         |<-----warmCost---->|

模块逻辑

TracePlugin在Application中初始化的时候,分别调用了init方法以及start方法。

public class TracePlugin extends Plugin {
    private static final String TAG = "Matrix.TracePlugin";

    private final TraceConfig traceConfig;
    private EvilMethodTracer evilMethodTracer;
    private StartupTracer startupTracer;
    private FrameTracer frameTracer;
    private AnrTracer anrTracer;

    public TracePlugin(TraceConfig config) {
        this.traceConfig = config;
    }

    @Override
    public void init(Application app, PluginListener listener) {
        super.init(app, listener);
        MatrixLog.i(TAG, "trace plugin init, trace config: %s", traceConfig.toString());
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
            MatrixLog.e(TAG, "[FrameBeat] API is low Build.VERSION_CODES.JELLY_BEAN(16), TracePlugin is not supported");
            unSupportPlugin();
            return;
        }

        anrTracer = new AnrTracer(traceConfig);

        frameTracer = new FrameTracer(traceConfig);

        evilMethodTracer = new EvilMethodTracer(traceConfig);

        startupTracer = new StartupTracer(traceConfig);
    }

    @Override
    public void start() {
        super.start();
        if (!isSupported()) {
            MatrixLog.w(TAG, "[start] Plugin is unSupported!");
            return;
        }
        MatrixLog.w(TAG, "start!");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {

                if (!UIThreadMonitor.getMonitor().isInit()) {
                    try {
                        UIThreadMonitor.getMonitor().init(traceConfig);
                    } catch (java.lang.RuntimeException e) {
                        MatrixLog.e(TAG, "[start] RuntimeException:%s", e);
                        return;
                    }
                }

                AppMethodBeat.getInstance().onStart();

                UIThreadMonitor.getMonitor().onStart();

                anrTracer.onStartTrace();

                frameTracer.onStartTrace();

                evilMethodTracer.onStartTrace();

                startupTracer.onStartTrace();
            }
        };

        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            runnable.run();
        } else {
            MatrixLog.w(TAG, "start TracePlugin in Thread[%s] but not in mainThread!", Thread.currentThread().getId());
            MatrixHandlerThread.getDefaultMainHandler().post(runnable);
        }
    }
}

在init方法中,对低于JB的设备不启用TracePlugin,并创建了ANR监控、帧率监控、慢方法监控以及启动耗时这4个功能的Tracer。在start中,会在主线程中启动UIThreadMonitor、AppMethodBeat以及四大Tracer。

TracePlugin的功能完全体现在了四大Tracer中,UIThreadMonitor、AppMethodBeat是这些Tracer起作用的基石。理解了这些基石,四大Tracer的分析就非常简单了。

此外,基于这两大基石,我们还可以写出更多好玩的东西:比如 后台渲染的检测,UIThreadMonitor判断后台渲染是否产生,由AppMethodBeat可以获取引发后台渲染的堆栈信息。

UIThreadMonitor

通过设置Looper中的printer,来判断Message的执行起止时间。然后hook Choreographer中的input animation traversal回调数组,向其中添加Runnable来获取每个操作的耗时。最后将这些数据抛出给各个Tracer作为判断的依据。

获取线程中每个Message的执行起止时间是在Matrix中是LooperMonitor类来实现的,UIThreadMonitor向该类注册一个回调,由此在对应回调中进行对应的操作。我们接着看UIThreadMonitor#init方法:

public void init(TraceConfig config) {
    if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
        throw new AssertionError("must be init in main thread!");
    }
    this.config = config;
    choreographer = Choreographer.getInstance();
    callbackQueueLock = reflectObject(choreographer, "mLock");
    callbackQueues = reflectObject(choreographer, "mCallbackQueues");

    addInputQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);
    addAnimationQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class);
    addTraversalQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class);
    frameIntervalNanos = reflectObject(choreographer, "mFrameIntervalNanos");

    LooperMonitor.register(new LooperMonitor.LooperDispatchListener() {
        @Override
        public boolean isValid() {
            return isAlive;
        }

        @Override
        public void dispatchStart() {
            super.dispatchStart();
            UIThreadMonitor.this.dispatchBegin();
        }

        @Override
        public void dispatchEnd() {
            super.dispatchEnd();
            UIThreadMonitor.this.dispatchEnd();
        }

    });
    this.isInit = true;
    MatrixLog.i(TAG, "[UIThreadMonitor] %s %s %s %s %s frameIntervalNanos:%s", callbackQueueLock == null, callbackQueues == null, addInputQueue == null, addTraversalQueue == null, addAnimationQueue == null, frameIntervalNanos);

    if (config.isDevEnv()) {
        addObserver(new LooperObserver() {
            @Override
            public void doFrame(String focusedActivityName, long start, long end, long frameCostMs, long inputCost, long animationCost, long traversalCost) {
                MatrixLog.i(TAG, "activityName[%s] frame cost:%sms [%s|%s|%s]ns", focusedActivityName, frameCostMs, inputCost, animationCost, traversalCost);
            }
        });
    }
}

UIThreadMonitor#init方法主要干了两件事:

  • 反射获取Choreographer中CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL三种类型的CallbackQueue的addCallbackLocked方法的句柄。
  • 向LooperMonitor注册Message执行开始的回调、执行结束的回调。这里的Message是指主线程中发生的所有Message,包括App自己的以及Framework中的,Choreographer中的自然也可以捕获到。

下面看看UIThreadMonitor#start方法,在里面初始化了需要检测的三种CallbackQueue的各种记录数组:

  • callbackExist:记录是否已经向该类型的CallbackQueue添加了Runnable,避免重复添加
  • queueStatus:记录CallbackQueue中添加的Runnable的运行状态,分为默认状态(DO_QUEUE_DEFAULT)、已经添加的状态(DO_QUEUE_BEGIN)、运行结束的状态(DO_QUEUE_END)
  • queueCost:记录上面某种类型的Runnable的执行起始的耗时,这可以反映出当前这一次执行CallbackQueue里面的任务耗时有多久。
@Override
public synchronized void onStart() {
    if (!isInit) {
        throw new RuntimeException("never init!");
    }
    if (!isAlive) {
        this.isAlive = true;
        synchronized (this) {
            MatrixLog.i(TAG, "[onStart] callbackExist:%s %s", Arrays.toString(callbackExist), Utils.getStack());
            callbackExist = new boolean[CALLBACK_LAST + 1];
        }
        queueStatus = new int[CALLBACK_LAST + 1];
        queueCost = new long[CALLBACK_LAST + 1];
        addFrameCallback(CALLBACK_INPUT, this, true);
    }
}

在UIThreadMonitor#onStart方法中,最后调用addFrameCallback方法将一个Runnable(自己)插到了INPUT类型的CallbackQueue的头部。CallbackQueue是一个单链表组织起来的队列,里面按照时间从小到大进行组织。

下面我们接着看一下addFrameCallback的实现,这里首先会判断某种type类型的callback是否已经添加,UIThreadMonitor是否已经启动等等检查,然后根据type取得需要invoke的方法句柄,然后调用该方法并设置callbackExist标志位。

在第26行中我们注意到对入参isAddHeader做出的值转换——!isAddHeader ? SystemClock.uptimeMillis() : -1:也就是说如果isAddHeader为true,这里的值就是-1,会在CallbackQueue执行时首先执行(系统内部不会将这里的time设置为一个负值,所以无论何时,这里都将会是第一个执行的);否则的话,传的是当前的时间戳,会根据值插入到单链表队列中。

private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
    if (callbackExist[type]) {
        MatrixLog.w(TAG, "[addFrameCallback] this type %s callback has exist! isAddHeader:%s", type, isAddHeader);
        return;
    }

    if (!isAlive && type == CALLBACK_INPUT) {
        MatrixLog.w(TAG, "[addFrameCallback] UIThreadMonitor is not alive!");
        return;
    }
    try {
        synchronized (callbackQueueLock) {
            Method method = null;
            switch (type) {
                case CALLBACK_INPUT:
                    method = addInputQueue;
                    break;
                case CALLBACK_ANIMATION:
                    method = addAnimationQueue;
                    break;
                case CALLBACK_TRAVERSAL:
                    method = addTraversalQueue;
                    break;
            }
            if (null != method) {
                method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
                callbackExist[type] = true;
            }
        }
    } catch (Exception e) {
        MatrixLog.e(TAG, e.toString());
    }
}

上面就是onStart方法干的事情,归根结底就是向Choreograpger注册了一个回调(即UIThreadMonitor自身),这样下次Vsync信号来到时,就会触发这个callback(UIThreadMonitor#run方法)。

然后我们看看UIThreadMonitor#run里面的代码,这里面涉及到三个新的方法:

  • doFrameBegin:设置isBelongFrame标志位为true,这标志着当前Frame已经被纳入了统计
  • doQueueBegin:更新对应type的queueStatus标志位为DO_QUEUE_BEGIN,并用queueCost[type]记下此时的时间
  • doQueueEnd:更新对应type的queueStatus标志位为DO_QUEUE_END,并用当前时间减去queueCost[type]的时间,这个时间为当前frame执行时此type的CallbackQueue执行的总耗时,记为queueCost[type]

因此,UIThreadMonitor#run就很好解释了:先声明该frame正在被统计,然后计算出CALLBACK_INPUT的队列执行耗时、CALLBACK_ANIMATION的队列执行耗时,以及CALLBACK_TRAVERSAL执行开始了。

@Override
public void run() {
    final long start = System.nanoTime();
    try {
        doFrameBegin(token);
        doQueueBegin(CALLBACK_INPUT);

        addFrameCallback(CALLBACK_ANIMATION, new Runnable() {

            @Override
            public void run() {
                doQueueEnd(CALLBACK_INPUT);
                doQueueBegin(CALLBACK_ANIMATION);
            }
        }, true);

        addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {

            @Override
            public void run() {
                doQueueEnd(CALLBACK_ANIMATION);
                doQueueBegin(CALLBACK_TRAVERSAL);
            }
        }, true);

    } finally {
        if (config.isDevEnv()) {
            MatrixLog.d(TAG, "[UIThreadMonitor#run] inner cost:%sns", System.nanoTime() - start);
        }
    }
}

那,为什么这里没有调用doQueueEnd(CALLBACK_TRAVERSAL)呢。我们研究Choreographer发现,在CALLBACK_TRAVERSAL之后还有一个CALLBACK_COMMIT,我们向CALLBACK_COMMIT这个队列添加一个callback就可以在合理的位置调用doQueueEnd(CALLBACK_TRAVERSAL)了。 但是很不幸,CALLBACK_COMMIT在Android 6.0及以后才会有。

为了兼容更早的版本,我们得想出其他办法:还记得上面提到的LooperMonitor吗,我们提到过LooperMonitor可以捕获到Message的执行的起始。Choreographer中的Vsync信号触发各种callback也是通过Android的消息机制来实现的,且该Message在执行完各种CallbackQueue就结束了,某种程度上来说,以Message的执行结束时间作CALLBACK_TRAVERSAL的结束时间也是可以的。

所以我们接着就来到了UIThreadMonitor#dispatchEnd方法:

private void dispatchEnd() {

    if (isBelongFrame) {
        doFrameEnd(token);
    }

    long start = token;
    long end = SystemClock.uptimeMillis();

    synchronized (observers) {
        for (LooperObserver observer : observers) {
            if (observer.isDispatchBegin()) {
                observer.doFrame(AppMethodBeat.getVisibleScene(), token, SystemClock.uptimeMillis(), isBelongFrame ? end - start : 0, queueCost[CALLBACK_INPUT], queueCost[CALLBACK_ANIMATION], queueCost[CALLBACK_TRAVERSAL]);
            }
        }
    }

    dispatchTimeMs[3] = SystemClock.currentThreadTimeMillis();
    dispatchTimeMs[1] = SystemClock.uptimeMillis();

    AppMethodBeat.o(AppMethodBeat.METHOD_ID_DISPATCH);

    synchronized (observers) {
        for (LooperObserver observer : observers) {
            if (observer.isDispatchBegin()) {
                observer.dispatchEnd(dispatchTimeMs[0], dispatchTimeMs[2], dispatchTimeMs[1], dispatchTimeMs[3], token, isBelongFrame);
            }
        }
    }

}

在方法的最开始,如果isBelongFrame标志位为true,表明是UIThreadMonitor#run方法已经执行过了,因此需要闭合整个frame的监控,闭合的这部分代码在doFrameEnd中。 然后会调用LooperObserver.doFrame方法通知观察者当前帧的耗时情况,调用LooperObserver.dispatchEnd通知观察者Message执行已经结束了。

这里没啥好说的,我们看一下闭合frame监控的doFrameEnd方法,这里调用了doQueueEnd(CALLBACK_TRAVERSAL)真正闭合了frame监控,然后恢复了queueStatus数组的状态,最后又调用了addFrameCallback(CALLBACK_INPUT, this, true)方法开始监控下一帧。

private void doFrameEnd(long token) {

    doQueueEnd(CALLBACK_TRAVERSAL);

    for (int i : queueStatus) {
        if (i != DO_QUEUE_END) {
            queueCost[i] = DO_QUEUE_END_ERROR;
            if (config.isDevEnv) {
                throw new RuntimeException(String.format("UIThreadMonitor happens type[%s] != DO_QUEUE_END", i));
            }
        }
    }
    queueStatus = new int[CALLBACK_LAST + 1];

    addFrameCallback(CALLBACK_INPUT, this, true);

    this.isBelongFrame = false;
}

说完了UIThreadMonitor#dispatchEnd方法,我们也顺便说说与之匹配的孪生方法UIThreadMonitor#dispatchBegin:

private void dispatchBegin() {
    token = dispatchTimeMs[0] = SystemClock.uptimeMillis();
    dispatchTimeMs[2] = SystemClock.currentThreadTimeMillis();
    AppMethodBeat.i(AppMethodBeat.METHOD_ID_DISPATCH);

    synchronized (observers) {
        for (LooperObserver observer : observers) {
            if (!observer.isDispatchBegin()) {
                observer.dispatchBegin(dispatchTimeMs[0], dispatchTimeMs[2], token);
            }
        }
    }
}

实际上该方法也很简单,保存下Message执行开始时的时间戳,然后调用LooperObserver#dispatchBegin方法回调出去。 dispatchTimeMs是一个长度为4的long数组,0、1分别表示的是Message执行开始、结束的uptimeMillis;2、3分别表示的是Message执行开始、结束的currentThreadTimeMillis。

至此,UIThreadMonitor的解析已经完毕,我们可以小结一下其作用:

  • 在主线程中Message执行开始时,调用LooperObserver#dispatchBegin(long beginMs, long cpuBeginMs, long token)通知外部
  • 在主线程中每个Message执行结束时,调用LooperObserver#doFrame(String focusedActivityName, long start, long end, long frameCostMs, long inputCostNs, long animationCostNs, long traversalCostNs)通知外部。在参数中,可以以frameCostMs的值反推isBelongFrame值。
  • 在主线程中Message执行完毕时,调用LooperObserver#dispatchEnd(long beginMs, long cpuBeginMs, long endMs, long cpuEndMs, long token, boolean isBelongFrame)通知外部

LooperMonitor

LooperMonitor的主要作用就是在Message执行前后回调出对应的方法。其实现原理是通过设置Looper#mLogging这个字段,让Message在执行前后打印出日志,然后根据日志的特点来判断是开始执行还是执行结束。可以看下Looper#loop方法的代码片段:

public static void loop() {
    ...
    for (;;) {
        Message msg = queue.next(); // might block
        ...
        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        ...
        try {
            msg.target.dispatchMessage(msg);
            ...
        } finally {
            ...
        }
        ...
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        ...
    }
}

首先LooperMonitor是一个饥汉单例的实现,其作用对象是主线程的Looper。 在LooperMonitor创建之后,首先调用resetPrinter方法保存原始的printer,然后使用LooperPrinter来装饰原始的printer并设置到Looper中,这样我们判断print的日志就知道Message的执行起始。

最后,向MessageQueue中添加了一个IdleHandler,在对应的queueIdle方法中会周期性(60s)的调用resetPrinter方法来保证Looper中的printer对象是我们自定义的LooperPrinter。

public class LooperMonitor implements MessageQueue.IdleHandler {
    ...
    private static final LooperMonitor mainMonitor = new LooperMonitor();

    public LooperMonitor(Looper looper) {
        Objects.requireNonNull(looper);
        this.looper = looper;
        resetPrinter();
        addIdleHandler(looper);
    }

    private LooperMonitor() {
        this(Looper.getMainLooper());
    }

    private static boolean isReflectLoggingError = false;

    private synchronized void resetPrinter() {
        Printer originPrinter = null;
        try {
            if (!isReflectLoggingError) {
                originPrinter = ReflectUtils.get(looper.getClass(), "mLogging", looper);
                if (originPrinter == printer && null != printer) {
                    return;
                }
            }
        } catch (Exception e) {
            isReflectLoggingError = true;
            Log.e(TAG, "[resetPrinter] %s", e);
        }

        if (null != printer) {
            MatrixLog.w(TAG, "maybe thread:%s printer[%s] was replace other[%s]!",
                    looper.getThread().getName(), printer, originPrinter);
        }
        looper.setMessageLogging(printer = new LooperPrinter(originPrinter));
        if (null != originPrinter) {
            MatrixLog.i(TAG, "reset printer, originPrinter[%s] in %s", originPrinter, looper.getThread().getName());
        }
    }

    private synchronized void addIdleHandler(Looper looper) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            looper.getQueue().addIdleHandler(this);
        } else {
            try {
                MessageQueue queue = ReflectUtils.get(looper.getClass(), "mQueue", looper);
                queue.addIdleHandler(this);
            } catch (Exception e) {
                Log.e(TAG, "[removeIdleHandler] %s", e);
            }
        }
    }
    ...
}

下面我们来到了LooperPrinter对象,直接看其println方法的实现,我们可以发现直接判断起始字符为不为>就可以知道是onDispatchStart还是onDispatchEnd:

class LooperPrinter implements Printer {
    public Printer origin;
    boolean isHasChecked = false;
    boolean isValid = false;

    LooperPrinter(Printer printer) {
        this.origin = printer;
    }

    @Override
    public void println(String x) {
        if (null != origin) {
            origin.println(x);
            if (origin == this) {
                throw new RuntimeException(TAG + " origin == this");
            }
        }

        if (!isHasChecked) {
            isValid = x.charAt(0) == '>' || x.charAt(0) == '<';
            isHasChecked = true;
            if (!isValid) {
                MatrixLog.e(TAG, "[println] Printer is inValid! x:%s", x);
            }
        }

        if (isValid) {
            dispatch(x.charAt(0) == '>', x);
        }

    }
}

private void dispatch(boolean isBegin, String log) {

    for (LooperDispatchListener listener : listeners) {
        if (listener.isValid()) {
            if (isBegin) {
                if (!listener.isHasDispatchStart) {
                    listener.onDispatchStart(log);
                }
            } else {
                if (listener.isHasDispatchStart) {
                    listener.onDispatchEnd(log);
                }
            }
        } else if (!isBegin && listener.isHasDispatchStart) {
            listener.dispatchEnd();
        }
    }
}

AppMethodBeat

AppMethodBeat会被Plugin在编译时进行调用,调用位置为Java方法的出入口以及Activity#onWindowFocusChange,Plugin会在这些位置分别调用其i(int methodId)、o(int methodId)以及at方法。 对于i/o方法里面的参数methodId,Plugin在插桩时会为每一个函数生成一个唯一的id,保证通过这个id可以找到对应的方法,methodId可以在编译文件app/build/outputs/mapping/debug/methodMapping.txt中得到。

下面是methodMapping文件中的示例: 1,1,sample.tencent.matrix.trace.TestTraceFragmentActivity ()V 2,1,sample.tencent.matrix.trace.TestFpsActivity ()V 上面分别表示:methodId,accessFlag,className methodName [desc]

插桩过程中会过滤一些不需要插桩的函数,结果输出在app/build/outputs/mapping/debug/methodMapping.txt中,其格式为className methodName [desc]。

插桩的这部分源码在matrix/matrix-android/matrix-gradle-plugin/src/main/java/com/tencent/matrix/trace/MethodTracer.java中,后面在解读Matrix Plugin时会进行详细说明。BTW,其实插桩插件流程总体可以分为两步:第一步收集工程中的class以及jar包中的class,在插桩完毕后进行回写,这一步都比较通用;第二步就是调用ASM进行插桩,这一部分才与需求相关。

AppMethodBeat#i、AppMethodBeat#o会将函数调用的i/o标志、methodId以及时间存到sBuffer = new long[100 * 10000]数组中,这个数组消耗内存约为8bytes * 100 * 10000 = 800_0000bytes = 7812.5kb = 7.629394531mb。这部分 内存消耗还是有点大的,是一个副作用吧。

我们说到,后面各种Tracer都是分析这个sBuffer数组来得到的函数调用堆栈,那么这个数组里面保存的数据有怎么样的格式呢?

Matrix在编译期会对全局的函数进行插桩,在运行期间每个函数的执行前后都会调用 AppMethodBeat.i/o 的方法,如果是在主线程中执行,则在函数的执行前后获取当前距离 MethodBeat 模块初始化的时间 offset(为了压缩数据,存进一个long类型变量中),并将当前执行的是 AppMethodBeat i或者o、mehtod id 及时间 offset,存放到一个 long 类型变量中,记录到一个预先初始化好的数组 long[] 中 index 的位置(预先分配记录数据的 buffer 长度为 100w,内存占用约 7.6M)。数据存储如下图1:

image.png

AppMethodBeat.i/o主要干的就是上面的这个事儿;在AppMethodBeat.at方法中,会在Activity#onWindowFocusChange时调用IAppMethodBeatListener#onActivityFocused方法。

小结一下,Matrix会在编译时对函数进行插桩,这样在运行期间每个函数的执行前后都会调用 AppMethodBeat.i/o 的方法,这些方法的调用记录会被数组保存起来,供后面各种Tracer进行函数调用堆栈分析。并且会在Activity#onWindowFocusChange处插入 AppMethodBeat.at 方法,当Activity获得焦点时调用回调通知外部。

image.png

编译期方法插桩代码分析

image.png

代码插桩的整体流程如上图。在打包过程中,hook生成Dex的Task任务,添加方法插桩的逻辑。我们的hook点是在Proguard之后,Class已经被混淆了,所以需要考虑类混淆的问题。 插桩代码逻辑大致分为三步:

  • hook原有的Task,执行自己的MatrixTraceTransform,并在最后执行原逻辑

  • 在方法插桩之前先要读取ClassMapping文件,获取混淆前方法、混淆后方法的映射关系并存储在MappingCollector中。

  • 之后遍历所有Dir、Jar中的Class文件,实际代码执行的时候遍历了两次。

    • 第一次遍历Class,获取所有待插桩的Method信息,并将信息输出到methodMap文件中;
    • 第二次遍历Class,利用ASM执行Method插桩逻辑。

hook原生打包流程

提供了插件:

class MatrixPlugin : Plugin<Project> {
    
    override fun apply(project: Project) {

        ...

        MatrixTasksManager().createMatrixTasks(
                project.extensions.getByName("android") as AppExtension,
                project,
                traceExtension,
                removeUnusedResourcesExtension
        )
    }
}
class MatrixTasksManager {

    fun createMatrixTasks(android: AppExtension,
                          project: Project,
                          traceExtension: MatrixTraceExtension,
                          removeUnusedResourcesExtension: MatrixRemoveUnusedResExtension) {

        createMatrixTraceTask(android, project, traceExtension)

    }

    private fun createMatrixTraceTask(
            android: AppExtension,
            project: Project,
            traceExtension: MatrixTraceExtension) {
        MatrixTraceCompat().inject(android, project, traceExtension)
    }
}
class MatrixTraceCompat : ITraceSwitchListener {

    var traceInjection: MatrixTraceInjection? = null

    init {
        if (VersionsCompat.greatThanOrEqual(AGPVersion.AGP_4_0_0)) {
            traceInjection = MatrixTraceInjection()
        }
    }

    override fun onTraceEnabled(enable: Boolean) {
        traceInjection?.onTraceEnabled(enable)
    }

    fun inject(appExtension: AppExtension, project: Project, extension: MatrixTraceExtension) {
        when {
            VersionsCompat.lessThan(AGPVersion.AGP_3_6_0) ->
                legacyInject(appExtension, project, extension)
            VersionsCompat.greatThanOrEqual(AGPVersion.AGP_4_0_0) ->
                traceInjection!!.inject(appExtension, project, extension)
            else -> Log.e(TAG, "Matrix does not support Android Gradle Plugin " +
                    "${VersionsCompat.androidGradlePluginVersion}!.")
        }
    }

    private fun legacyInject(appExtension: AppExtension,
                             project: Project,
                             extension: MatrixTraceExtension) {

        project.afterEvaluate {

            if (!extension.isEnable) {
                return@afterEvaluate
            }

            appExtension.applicationVariants.all {
                MatrixTraceLegacyTransform.inject(extension, project, it)
            }
        }
    }
}

将实际执行的Transform换成了MatrixTraceTransform

public static void inject(Project project, def variant) {
        //获取Matrix trace的gradle配置参数
        def configuration = project.matrix.trace
        //hook的Task名
        String hackTransformTaskName = getTransformTaskName(
                configuration.hasProperty('customDexTransformName') ? configuration.customDexTransformName : "",
                "",variant.name
        )
        //同上
        String hackTransformTaskNameForWrapper = getTransformTaskName(
                configuration.hasProperty('customDexTransformName') ? configuration.customDexTransformName : "",
                "Builder",variant.name
        )

        project.logger.info("prepare inject dex transform :" + hackTransformTaskName +" hackTransformTaskNameForWrapper:"+hackTransformTaskNameForWrapper)

        project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
            @Override
            public void graphPopulated(TaskExecutionGraph taskGraph) {
                for (Task task : taskGraph.getAllTasks()) {
                    //找到需要hook的Task名称
                    if ((task.name.equalsIgnoreCase(hackTransformTaskName) || task.name.equalsIgnoreCase(hackTransformTaskNameForWrapper))
                            && !(((TransformTask) task).getTransform() instanceof MatrixTraceTransform)) {
                        project.logger.warn("find dex transform. transform class: " + task.transform.getClass() + " . task name: " + task.name)
                        project.logger.info("variant name: " + variant.name)
                        Field field = TransformTask.class.getDeclaredField("transform")
                        field.setAccessible(true)
                        //反射替换成MatrixTraceTransform,并将原transform传入,最后执行原transform逻辑
                        field.set(task, new MatrixTraceTransform(project, variant, task.transform))
                        project.logger.warn("transform class after hook: " + task.transform.getClass())
                        break
                    }
                }
            }
        })
    }

MatrixTraceTransform主要逻辑在transform方法中

@Override
    public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        long start = System.currentTimeMillis()
        //是否增量编译
        final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental()
        //transform的结果,重定向输出到这个目录
        final File rootOutput = new File(project.matrix.output, "classes/${getName()}/")
        if (!rootOutput.exists()) {
            rootOutput.mkdirs()
        }
        final TraceBuildConfig traceConfig = initConfig()
        Log.i("Matrix." + getName(), "[transform] isIncremental:%s rootOutput:%s", isIncremental, rootOutput.getAbsolutePath())
        //获取Class混淆的mapping信息,存储到mappingCollector中
        final MappingCollector mappingCollector = new MappingCollector()
        File mappingFile = new File(traceConfig.getMappingPath());
        if (mappingFile.exists() && mappingFile.isFile()) {
            MappingReader mappingReader = new MappingReader(mappingFile);
            mappingReader.read(mappingCollector)
        }

        Map<File, File> jarInputMap = new HashMap<>()
        Map<File, File> scrInputMap = new HashMap<>()

        transformInvocation.inputs.each { TransformInput input ->
            input.directoryInputs.each { DirectoryInput dirInput ->
                //收集、重定向目录中的class
                collectAndIdentifyDir(scrInputMap, dirInput, rootOutput, isIncremental)
            }
            input.jarInputs.each { JarInput jarInput ->
                if (jarInput.getStatus() != Status.REMOVED) {
                    //收集、重定向jar包中的class
                    collectAndIdentifyJar(jarInputMap, scrInputMap, jarInput, rootOutput, isIncremental)
                }
            }
        }
        //收集需要插桩的方法信息,每个插桩信息封装成TraceMethod对象
        MethodCollector methodCollector = new MethodCollector(traceConfig, mappingCollector)
        HashMap<String, TraceMethod> collectedMethodMap = methodCollector.collect(scrInputMap.keySet().toList(), jarInputMap.keySet().toList())
       //执行插桩逻辑,在需要插桩方法的入口、出口添加MethodBeat的i/o逻辑
        MethodTracer methodTracer = new MethodTracer(traceConfig, collectedMethodMap, methodCollector.getCollectedClassExtendMap())
        methodTracer.trace(scrInputMap, jarInputMap)
        //执行原transform的逻辑;默认transformClassesWithDexBuilderForDebug这个task会将Class转换成Dex
        origTransform.transform(transformInvocation)
        Log.i("Matrix." + getName(), "[transform] cost time: %dms", System.currentTimeMillis() - start)
    }

收集Dir中的Class信息

private void collectAndIdentifyDir(Map<File, File> dirInputMap, DirectoryInput input, File rootOutput, boolean isIncremental) {
        final File dirInput = input.file
        final File dirOutput = new File(rootOutput, input.file.getName())
        if (!dirOutput.exists()) {
            dirOutput.mkdirs()
        }
        //增量编译
        if (isIncremental) {
            if (!dirInput.exists()) {
                dirOutput.deleteDir()
            } else {
                final Map<File, Status> obfuscatedChangedFiles = new HashMap<>()
                final String rootInputFullPath = dirInput.getAbsolutePath()
                final String rootOutputFullPath = dirOutput.getAbsolutePath()
                input.changedFiles.each { Map.Entry<File, Status> entry ->
                    final File changedFileInput = entry.getKey()
                    final String changedFileInputFullPath = changedFileInput.getAbsolutePath()
                    //增量编译模式下之前的build输出已经重定向到dirOutput;替换成output的目录
                    final File changedFileOutput = new File(
                            changedFileInputFullPath.replace(rootInputFullPath, rootOutputFullPath)
                    )
                    final Status status = entry.getValue()
                    switch (status) {
                        case Status.NOTCHANGED:
                            break
                        case Status.ADDED:
                        case Status.CHANGED:
                            //新增、修改的Class文件,此次需要扫描
                            dirInputMap.put(changedFileInput, changedFileOutput)
                            break
                        case Status.REMOVED:
                            //删除的Class文件,将文件直接删除
                            changedFileOutput.delete()
                            break
                    }
                    obfuscatedChangedFiles.put(changedFileOutput, status)
                }
                replaceChangedFile(input, obfuscatedChangedFiles)
            }
        } else {
            //全量编译模式下,所有的Class文件都需要扫描
            dirInputMap.put(dirInput, dirOutput)
        }
        //反射input,将dirOutput设置为其输出目录
        replaceFile(input, dirOutput)
    }

反射替换输出目录的代码:

  protected void replaceFile(QualifiedContent input, File newFile) {
        final Field fileField = ReflectUtil.getDeclaredFieldRecursive(input.getClass(), 'file')
        fileField.set(input, newFile
    }

类似的,收集Jar中的Class信息


   private void collectAndIdentifyJar(Map<File, File> jarInputMaps, Map<File, File> dirInputMaps, JarInput input, File rootOutput, boolean isIncremental) {
        final File jarInput = input.file
        final File jarOutput = new File(rootOutput, getUniqueJarName(jarInput))
        if (IOUtil.isRealZipOrJar(jarInput)) {
            switch (input.status) {
                case Status.NOTCHANGED:
                    if (isIncremental) {
                        break
                    }
                case Status.ADDED:
                case Status.CHANGED:
                    jarInputMaps.put(jarInput, jarOutput)
                    break
                case Status.REMOVED:
                    break
            }
        } else {
            ...
            //这部分代码可忽略,微信AutoDex自定义的文件结构
        }

        replaceFile(input, jarOutput)
    }

第一次遍历Class,收集待插桩method

public HashMap collect(List<File> srcFolderList, List<File> dependencyJarList) {
        mTraceConfig.parseBlackFile(mMappingCollector);
        //获取base模块已经收集到的待插桩方法
        File originMethodMapFile = new File(mTraceConfig.getBaseMethodMap());
        getMethodFromBaseMethod(originMethodMapFile);
        
        Log.i(TAG, "[collect] %s method from %s", mCollectedMethodMap.size(), mTraceConfig.getBaseMethodMap());
        //转换为混淆后的方法名
        retraceMethodMap(mMappingCollector, mCollectedMethodMap);
        //仅收集目录、jar包中的class信息
        collectMethodFromSrc(srcFolderList, true);
        collectMethodFromJar(dependencyJarList, true);
        //收集目录、jar包中的method信息
        collectMethodFromSrc(srcFolderList, false);
        collectMethodFromJar(dependencyJarList, false);
        Log.i(TAG, "[collect] incrementCount:%s ignoreMethodCount:%s", mIncrementCount, mIgnoreCount);
        //存储待插桩的方法信息到文件
        saveCollectedMethod(mMappingCollector);
        //存储不需要插桩的方法信息到文件(包括黑名单中的方法)
        saveIgnoreCollectedMethod(mMappingCollector);
        //返回待插桩的方法集合
        return mCollectedMethodMap;

    }

收集method信息的逻辑类似,以下面代码为例(字节码相关操作使用了ASM)

  private void innerCollectMethodFromSrc(File srcFile, boolean isSingle) {
        ArrayList<File> classFileList = new ArrayList<>();
        if (srcFile.isDirectory()) {
            listClassFiles(classFileList, srcFile);
        } else {
            classFileList.add(srcFile);
        }

        for (File classFile : classFileList) {
            InputStream is = null;
            try {
                is = new FileInputStream(classFile);
                ClassReader classReader = new ClassReader(is);
                ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                ClassVisitor visitor;
                if (isSingle) {
                    //仅收集Class信息
                    visitor = new SingleTraceClassAdapter(Opcodes.ASM5, classWriter);
                } else {
                    //收集Method信息
                    visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
                }
                classReader.accept(visitor, 0);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    is.close();
                } catch (Exception e) {
                    // ignore
                }
            }
        }
    }

个人感觉SingleTraceClassAdapter好像是多余的,一个TraceClassAdapter可以搞定收集Class、Method的信息

    private class TraceClassAdapter extends ClassVisitor {
        private String className;
        private boolean isABSClass = false;
        private boolean hasWindowFocusMethod = false;

        TraceClassAdapter(int i, ClassVisitor classVisitor) {
            super(i, classVisitor);
        }

        @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces);
            this.className = name;
            if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
                this.isABSClass = true;
            }
            //存储一个 类->父类 的map(用于查找Activity的子类)
            mCollectedClassExtendMap.put(className, superName);

        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc,
                                         String signature, String[] exceptions) {
            if (isABSClass) {
                return super.visitMethod(access, name, desc, signature, exceptions);
            } else {
                if (!hasWindowFocusMethod) {
                    //该方法是否与onWindowFocusChange方法的签名一致(该类中是否复写了onWindowFocusChange方法,Activity不用考虑Class混淆)
                    hasWindowFocusMethod = mTraceConfig.isWindowFocusChangeMethod(name, desc);
                }
                //CollectMethodNode中执行method收集操作
                return new CollectMethodNode(className, access, name, desc, signature, exceptions);
            }
        }

        @Override
        public void visitEnd() {
            super.visitEnd();
            // collect Activity#onWindowFocusChange
            //onWindowFocusChange方法统一给一个-1的方法id
            TraceMethod traceMethod = TraceMethod.create(-1, Opcodes.ACC_PUBLIC, className,
                    TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
            //没有过复写onWindowFocusChange,后续会在该类中插入一个onWindowFocusChange方法,此处先记录一下这个会被插桩的方法
            if (!hasWindowFocusMethod && mTraceConfig.isActivityOrSubClass(className, mCollectedClassExtendMap) && mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector)) {
                mCollectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
            }
        }

    }

如果子类Activity复写了onWindowFocusChange方法,其对应的methodId就不为-1了;这块逻辑感觉有点问题~~

    private class CollectMethodNode extends MethodNode {
        private String className;
        private boolean isConstructor;


        CollectMethodNode(String className, int access, String name, String desc,
                          String signature, String[] exceptions) {
            super(Opcodes.ASM5, access, name, desc, signature, exceptions);
            this.className = className;
        }

        @Override
        public void visitEnd() {
            super.visitEnd();
            TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);

            if ("<init>".equals(name) /*|| "<clinit>".equals(name)*/) {
                isConstructor = true;
            }
            // filter simple methods
            //忽略空方法、get/set方法、没有局部变量的简单方法,
            if ((isEmptyMethod() || isGetSetMethod() || isSingleMethod())
                    && mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector)) {
                mIgnoreCount++;
                mCollectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
                return;
            }

            //不在黑名单中的方法加入待插桩的集合;在黑名单中的方法加入ignore插桩的集合
            if (mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector) && !mCollectedMethodMap.containsKey(traceMethod.getMethodName())) {
                traceMethod.id = mMethodId.incrementAndGet();
                mCollectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
                mIncrementCount++;
            } else if (!mTraceConfig.isNeedTrace(traceMethod.className, mMappingCollector)
                    && !mCollectedBlackMethodMap.containsKey(traceMethod.className)) {
                mIgnoreCount++;
                mCollectedBlackMethodMap.put(traceMethod.getMethodName(), traceMethod);
            }

        }
    }

第二次遍历Class,执行method插桩逻辑

入口是MethodTracer的trace方法

  public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList) {
        traceMethodFromSrc(srcFolderList);
        traceMethodFromJar(dependencyJarList);
    }

分别对目录、jar包插桩

private void innerTraceMethodFromSrc(File input, File output) {

        ...
                if (mTraceConfig.isNeedTraceClass(classFile.getName())) {
                    is = new FileInputStream(classFile);
                    ClassReader classReader = new ClassReader(is);
                    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                    ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
                    classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
        ...
    }
    
    private void innerTraceMethodFromJar(File input, File output) {
       ...
                if (mTraceConfig.isNeedTraceClass(zipEntryName)) {
                    InputStream inputStream = zipFile.getInputStream(zipEntry);
                    ClassReader classReader = new ClassReader(inputStream);
                    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                    ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
                    classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
                    byte[] data = classWriter.toByteArray();
                    InputStream byteArrayInputStream = new ByteArrayInputStream(data);
                    ZipEntry newZipEntry = new ZipEntry(zipEntryName);
                    FileUtil.addZipEntry(zipOutputStream, newZipEntry, byteArrayInputStream);
    ...
    }

核心逻辑在TraceClassAdapter中

private class TraceClassAdapter extends ClassVisitor {

        private String className;
        private boolean isABSClass = false;
        private boolean isMethodBeatClass = false;
        private boolean hasWindowFocusMethod = false;

        TraceClassAdapter(int i, ClassVisitor classVisitor) {
            super(i, classVisitor);
        }

        @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces);
            this.className = name;
            //是否是抽象类、接口
            if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
                this.isABSClass = true;
            }
            //是否是MethodBeat类
            if (mTraceConfig.isMethodBeatClass(className, mCollectedClassExtendMap)) {
                isMethodBeatClass = true;
            }
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc,
                                         String signature, String[] exceptions) {
             //抽象类、接口不插桩
            if (isABSClass) {
                return super.visitMethod(access, name, desc, signature, exceptions);
            } else {
                if (!hasWindowFocusMethod) {
                    //是否是onWindowFocusChange方法
                    hasWindowFocusMethod = mTraceConfig.isWindowFocusChangeMethod(name, desc);
                }
                MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
                return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className,
                        hasWindowFocusMethod, isMethodBeatClass);
            }
        }


        @Override
        public void visitEnd() {
            TraceMethod traceMethod = TraceMethod.create(-1, Opcodes.ACC_PUBLIC, className,
                    TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
            //如果Activity的子类没有onWindowFocusChange方法,插入一个onWindowFocusChange方法
            if (!hasWindowFocusMethod && mTraceConfig.isActivityOrSubClass(className, mCollectedClassExtendMap)
                    && mCollectedMethodMap.containsKey(traceMethod.getMethodName())) {
                insertWindowFocusChangeMethod(cv);
            }
            super.visitEnd();
        }
    }

在待插桩方法的入口、出口添加对应逻辑

rivate class TraceMethodAdapter extends AdviceAdapter {

        private final String methodName;
        private final String name;
        private final String className;
        private final boolean hasWindowFocusMethod;
        private final boolean isMethodBeatClass;

        protected TraceMethodAdapter(int api, MethodVisitor mv, int access, String name, String desc, String className,
                                     boolean hasWindowFocusMethod, boolean isMethodBeatClass) {
            super(api, mv, access, name, desc);
            TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);
            this.methodName = traceMethod.getMethodName();
            this.isMethodBeatClass = isMethodBeatClass;
            this.hasWindowFocusMethod = hasWindowFocusMethod;
            this.className = className;
            this.name = name;
        }

        @Override
        protected void onMethodEnter() {
            TraceMethod traceMethod = mCollectedMethodMap.get(methodName);
            if (traceMethod != null) {
                //函数入口处添加逻辑;
                //没有单独处理onWindowFocusChange,对于已经复写onWindowFocusChange的Activity子类,会有问题?
                traceMethodCount.incrementAndGet();
                mv.visitLdcInsn(traceMethod.id);
                mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
            }
        }

        @Override
        protected void onMethodExit(int opcode) {
            TraceMethod traceMethod = mCollectedMethodMap.get(methodName);
            if (traceMethod != null) {
                if (hasWindowFocusMethod && mTraceConfig.isActivityOrSubClass(className, mCollectedClassExtendMap)
                        && mCollectedMethodMap.containsKey(traceMethod.getMethodName())) {
                    TraceMethod windowFocusChangeMethod = TraceMethod.create(-1, Opcodes.ACC_PUBLIC, className,
                            TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
                    if (windowFocusChangeMethod.equals(traceMethod)) {
                        //onWindowFocusChange方法统一添加method id = -1的逻辑
                        traceWindowFocusChangeMethod(mv);
                    }
                }
                //函数出口处添加逻辑
                traceMethodCount.incrementAndGet();
                mv.visitLdcInsn(traceMethod.id);
                mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
            }
        }
    }

对于没有复现onWindowFocusChange方法的Activity子类,插入一个onWindowFocusChange方法

  private void insertWindowFocusChangeMethod(ClassVisitor cv) {
        MethodVisitor methodVisitor = cv.visitMethod(Opcodes.ACC_PUBLIC, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD,
                TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS, null, null);
        methodVisitor.visitCode();
        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
        methodVisitor.visitVarInsn(Opcodes.ILOAD, 1);
        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, TraceBuildConstants.MATRIX_TRACE_ACTIVITY_CLASS, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD,
                TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS, false);
        traceWindowFocusChangeMethod(methodVisitor);
        methodVisitor.visitInsn(Opcodes.RETURN);
        methodVisitor.visitMaxs(2, 2);
        methodVisitor.visitEnd();

    }

至此,编译期插桩的逻辑就结束了;在运行期,检测到某个方法异常时,会上报一个method id,后端通过下图的method id到method name的映射关系,追查到有问题的方法

image.png