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 数组记录的,格式如下:
- 堆栈聚类问题: 如果将收集的原始数据进行上报,数据量很大而且后台很难聚类有问题的堆栈,所以在上报之前需要对采集的数据进行简单的整合及裁剪,并分析出一个能代表卡顿堆栈的 key,方便后台聚合。具体的方法是通过遍历采集的 buffer ,相邻 i 与 o 为一次完整函数执行,计算出一个调用树及每个函数执行耗时,并对每一级中的一些相同执行函数做聚合,最后通过一个简单策略,分析出主要耗时的那一级函数,作为代表卡顿堆栈的key。
插桩打点操作的类就是AppMethodBeat,该类会在编译期对函数的出入进行插桩i(methodId)/o(methodId),并在Activity#onWindowFocusChanged方法中插入at方法,供后面各种tracer使用。插桩的核心代码在插件中的MethodTracer。
插桩之外,我们还需要监控主线程MessageQueue(LooperMonitor)以及Choreographer(UIThreadMonitor),从里面抽象出一个可以通知Message开始执行、执行完成、Choreographer开始渲染的接口(LooperObserver)。基于这个接口,我们可以开始监控的实现。
- 帧率监控FrameTracer 在UIThreadMonitor中会通过LooperMonitor监听所有主线程的Message的执行,同时会向Choreographer#callbackQueues中插入一个回调来监听CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL这三种事务的执行耗时。
这样当Choreographer#FrameHandler开始执行的vsync时,UIThreadMonitor就可以捕获到vsync执行的起止时间,以及doFrame时各个部分的耗时。然后可以通过一系列计算来计算出帧率。实际上,只需要知道渲染每一帧的起止时间就可以算出帧率了。
-
慢函数监控EvilMethodTracer 通过监控主线程中每个Message执行的起止时间,如果时间差超过一定的阈值,就认为发生了慢函数调用。此时可以通过AppMethodBeat中的数据,分析出这段时间内函数执行的堆栈信息,以及每个函数执行的耗时。这样,慢函数无所遁形了。
-
ANR监控AnrTracer ANR的监控更加简单了,在主线程中一般认为超过5s就会发生ANR。所以在Message开始执行时,抛出一个5s后爆炸的炸弹,在Message执行完毕之后remove。若这颗炸弹最终还是爆炸了,那就说明发生了ANR。此时还是通过分析AppMethodBeat中的数据得到函数执行的堆栈以及耗时。
-
启动耗时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:
AppMethodBeat.i/o主要干的就是上面的这个事儿;在AppMethodBeat.at方法中,会在Activity#onWindowFocusChange时调用IAppMethodBeatListener#onActivityFocused方法。
小结一下,Matrix会在编译时对函数进行插桩,这样在运行期间每个函数的执行前后都会调用 AppMethodBeat.i/o 的方法,这些方法的调用记录会被数组保存起来,供后面各种Tracer进行函数调用堆栈分析。并且会在Activity#onWindowFocusChange处插入 AppMethodBeat.at 方法,当Activity获得焦点时调用回调通知外部。
编译期方法插桩代码分析
代码插桩的整体流程如上图。在打包过程中,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的映射关系,追查到有问题的方法