插件入口
Android Gradle Plugin入口:src/main/resources/META-INF/gradle-plugins
在matrix-gradle-plugin
对应目录下有个com.tencent.matrix-plugin.properties
,表示插件名称:``com.tencent.matrix-plugin,使用的地方:
apply plugin: 'com.tencent.matrix-plugin'`
//文件名:com.tencent.matrix-plugin.properties (.properties是后缀)
implementation-class=com.tencent.matrix.plugin.MatrixPlugin
指定了实现类MatrixPlugin
class MatrixPlugin : Plugin<Project> {
//插件执行的入口
override fun apply(project: Project) {
//添加Extension,用于提供给用户自定义配置选项
val matrix = project.extensions.create("matrix", MatrixExtension::class.java)
//matrix闭包下扩展一个trace对象
val traceExtension = (matrix as ExtensionAware).extensions.create("trace", MatrixTraceExtension::class.java)
val removeUnusedResourcesExtension = matrix.extensions.create("removeUnusedResources", MatrixRemoveUnusedResExtension::class.java)
//必须是应用才去执行后面的插桩操作
if (!project.plugins.hasPlugin("com.android.application")) {
throw GradleException("Matrix Plugin, Android Application plugin required.")
}
//需要在project.afterEvaluate方法中获取扩展配置,因为apply plugin的执行时机早于扩展配置。
project.afterEvaluate {
//设置日志等级
Log.setLogLevel(matrix.logLevel)
}
//进入MatrixTasksManager
MatrixTasksManager().createMatrixTasks(
project.extensions.getByName("android") as AppExtension,
project,
traceExtension,
removeUnusedResourcesExtension
)
}
}
project.extensions.create
方法来获取在 matrix闭包中定义的内容并通过反射将闭包的内容转换成一个 MatrixExtension 对象(注意需要在project.afterEvaluate
方法中获取扩展配置,因为apply plugin的执行时机早于扩展配置)。我们可以在app的build.gradle中使用matrix去配置扩展属性,代码如:
apply plugin: 'com.tencent.matrix-plugin'
matrix {
trace {
//matrix配置插桩是否开启
enable = true
//方法映射
baseMethodMapFile = "${project.projectDir}/matrixTrace/methodMapping.txt"
//插桩黑名单
blackListFile = "${project.projectDir}/matrixTrace/blackMethodList.txt"
}
}
MatrixTasksManager().createMatrixTasks()会根据gradle版本创建两个不同的Transform:高版本MatrixTraceTransform和低版本MatrixTraceLegacyTransform,两个都会走核心类MatrixTrace
的doTransform方法,分为关键的3步:
第一步:解析
/**
* step 1 [Parse]
*/
//解析mapping文件和处理黑名单等
futures.add(executor.submit(ParseMappingTask(
mappingCollector, collectedMethodMap, methodId, config)))
for (file in classInputs) {
if (file.isDirectory) {
//收集class文件
futures.add(executor.submit(CollectDirectoryInputTask(params...)))
} else {
//收集jar文件
futures.add(executor.submit(CollectJarInputTask(params...)))
}
}
ParseMappingTask用来解析mapping.txt文件的,因为这个时候class已经被混淆过了,混淆后插桩避免插桩导致某些编译器优化失效。
val mappingFile = File(config.mappingDir, "mapping.txt")
if (mappingFile.isFile) {
val mappingReader = MappingReader(mappingFile)
//解析mapping
mappingReader.read(mappingCollector)
}
//解析黑名单
val size = config.parseBlockFile(mappingCollector)
//处理已分配methodId的函数列表
getMethodFromBaseMethod(baseMethodMapFile, collectedMethodMap)
调用MappingReader去解析,得到类、方法在混淆前和混淆后的映射关系,保存在MappingCollector中。
创建 CollectDirectoryInputTask和CollectJarInputTask,支持增量处理,分别收集 class 和 jar文件到 map。
第二步:收集
/**
* step 2 [Collection]
*/
val methodCollector = MethodCollector(executor, mappingCollector, methodId, config,collectedMethodMap)
methodCollector.collect(dirInputOutMap.keys, jarInputOutMap.keys)
public void collect(Set<File> srcFolderList, Set<File> dependencyJarList) throws ExecutionException, InterruptedException {
//...省略 class文件直接存classFileList,文件夹则递归取出class存classFileList
for (File classFile : classFileList) {
//针对每一个class文件执行CollectSrcTask
futures.add(executor.submit(new CollectSrcTask(classFile)));
}
for (File jarFile : dependencyJarList) {
//针对每一个jar文件执行CollectSrcTask
futures.add(executor.submit(new CollectJarTask(jarFile)));
}
//...省略 futures等待任务完成等
//不需要插桩的方法名写入ignoreMethodMapFilePath文件中
saveIgnoreCollectedMethod(mappingCollector);
// 将被插桩的方法名存入methodMapping.txt
saveCollectedMethod(mappingCollector);
}
CollectSrcTask和CollectJarTask逻辑相同,一个处理class,一个处理jar,使用了ASM
//不需要把这个类的整个结构读取进来,就可以用流的方法来处理字节码文件
is = new FileInputStream(classFile);
//读取已经编译好的.class文件
ClassReader classReader = new ClassReader(is);
//用于重新构建编译后的类,生成新的类的字节码文件
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
//对于字节码文件中不同的区域有不同的Visitor,如访问类的ClassVisitor
ClassVisitor visitor = new TraceClassAdapter(AgpCompat.getAsmApi(), classWriter);
classReader.accept(visitor, 0);
关键操作在TraceClassAdapter
private class TraceClassAdapter extends 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;
//接口或抽象类,记录isABSClass为true
if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
this.isABSClass = true;
}
//记录类与父类,方便分析继承关系
collectedClassExtendMap.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 {
//记录类中是否含有onWindowFocusChange方法(后续应用应用和页面启动分析)
if (!hasWindowFocusMethod) {
hasWindowFocusMethod = isWindowFocusChangeMethod(name, desc);
}
//进入CollectMethodNode
return new CollectMethodNode(className, access, name, desc, signature, exceptions);
}
}
}
CollectMethodNode用来记录方法信息,在方法访问结束会调用visitEnd
@Override
public void visitEnd() {
super.visitEnd();
//将方法信息封装成TraceMethod
TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);
//是否构造方法
if ("<init>".equals(name)) {
isConstructor = true;
}
//判断是否需要插桩
boolean isNeedTrace = isNeedTrace(configuration, traceMethod.className, mappingCollector);
// 过滤一些简单的方法,空方法、get/set方法等
if ((isEmptyMethod() || isGetSetMethod() || isSingleMethod())
&& isNeedTrace) {
ignoreCount.incrementAndGet();
//一些简单方法(即不需要插桩的方法)存collectedIgnoreMethodMap
collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
return;
}
if (isNeedTrace && !collectedMethodMap.containsKey(traceMethod.getMethodName())) {
traceMethod.id = methodId.incrementAndGet();
//不在黑名单中(即需要插桩的方法)存collectedMethodMap
collectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
incrementCount.incrementAndGet();
} else if (!isNeedTrace && !collectedIgnoreMethodMap.containsKey(traceMethod.className)) {
ignoreCount.incrementAndGet();
//在黑名单中(即不需要插桩的方法)存collectedIgnoreMethodMap
collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
}
}
第二步收集就是将class中需要插桩和不需要插桩分别存入两个map
第三步:插桩
/**
* step 3 [Trace]
*/
val methodTracer = MethodTracer(executor, mappingCollector, config, methodCollector.collectedMethodMap, methodCollector.collectedClassExtendMap)
//合并src和jar
val allInputs = ArrayList<File>().also {
it.addAll(dirInputOutMap.keys)
it.addAll(jarInputOutMap.keys)
}
val traceClassLoader = TraceClassLoader.getClassLoader(project, allInputs)
//插桩
methodTracer.trace(dirInputOutMap, jarInputOutMap, traceClassLoader, skipCheckClass)
接下来进入MethodTracer进行真正的插桩了
public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList, ClassLoader classLoader, boolean ignoreCheckClass) throws ExecutionException, InterruptedException {
//...
//src插桩
traceMethodFromSrc(srcFolderList, futures, classLoader, ignoreCheckClass);
//jar插桩
traceMethodFromJar(dependencyJarList, futures, classLoader, ignoreCheckClass);
//...
}
private void traceMethodFromSrc(Map<File, File> srcMap, List<Future> futures, final ClassLoader classLoader, final boolean skipCheckClass) {
if (null != srcMap) {
for (Map.Entry<File, File> entry : srcMap.entrySet()) {
futures.add(executor.submit(new Runnable() {
@Override
public void run() {
//子线程执行插桩
innerTraceMethodFromSrc(entry.getKey(), entry.getValue(), classLoader, skipCheckClass);
}
}));
}
}
}
traceMethodFromSrc和traceMethodFromJar分别对src和jar插桩,以traceMethodFromSrc为例,会在子线程中执行innerTraceMethodFromSrc
private void innerTraceMethodFromSrc(File input, File output, ClassLoader classLoader, boolean ignoreCheckClass) {
//...
is = new FileInputStream(classFile);
ClassReader classReader = new ClassReader(is);
ClassWriter classWriter = new TraceClassWriter(ClassWriter.COMPUTE_FRAMES, classLoader);
ClassVisitor classVisitor = new TraceClassAdapter(AgpCompat.getAsmApi(), classWriter);
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
is.close();
byte[] data = classWriter.toByteArray();
if (!ignoreCheckClass) {
try {
ClassReader cr = new ClassReader(data);
ClassWriter cw = new ClassWriter(0);
ClassVisitor check = new CheckClassAdapter(cw);
cr.accept(check, ClassReader.EXPAND_FRAMES);
} catch (Throwable e) {
System.err.println("trace output ERROR : " + e.getMessage() + ", " + classFile);
traceError = true;
}
}
//...
}
TraceClassAdapter
/**
* ClassVisitor用于访问和修改Java类文件中的字节码
*/
private class TraceClassAdapter extends ClassVisitor {
/**
* visit开始访问类文件时被调用的方法
*
* @param version 类文件的版本
* @param access 类的访问修饰符,如ACC_PUBLIC, ACC_FINAL (see {@link Opcodes})
* @param name 类的全名
* @param signature 类的签名
* @param superName 超类的全名
* @param interfaces 接口的全名数组
*/
@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;
this.superName = superName;
//是否是Activity或Activity子类
this.isActivityOrSubClass = isActivityOrSubClass(className, collectedClassExtendMap);
//是否需要插桩
this.isNeedTrace = MethodCollector.isNeedTrace(configuration, className, mappingCollector);
//抽象类或者接口
if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
this.isABSClass = true;
}
}
/**
* 用于访问方法信息(单个方法)
*
* @param access 方法的访问修饰符.
* @param name 方法的名称.
* @param desc 方法的描述符,描述了方法的参数和返回类型.
* @param signature 方法的签名,通常用于泛型方法.
* @param exceptions 方法可能抛出的异常类型数组.
* @return MethodVisitor对象,负责处理方法的字节码
*/
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
//是否有Activity的onWindowFocusChanged方法
if (!hasWindowFocusMethod) {
hasWindowFocusMethod = MethodCollector.isWindowFocusChangeMethod(name, desc);
}
//抽象类或接口不插桩
if (isABSClass) {
return super.visitMethod(access, name, desc, signature, exceptions);
} else {
//方法插桩
MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className,
hasWindowFocusMethod, isActivityOrSubClass, isNeedTrace);
}
}
/**
* 类访问结束
*/
@Override
public void visitEnd() {
if (!hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
//插入onWindowFocusChanged,并在其中加入自己的逻辑 AppMethodBeat.at
insertWindowFocusChangeMethod(cv, className, superName);
}
super.visitEnd();
}
}
ClassVisitor
用于访问和修改Java类文件中的字节码
visit
方法开始访问类文件时被调用(做了一些基本判断,是否是Activity,是否需要插桩,判断抽象类和接口)visitMethod
用于访问方法信息(是否是onWindowFocusChanged方法,过滤了抽象类和接口,方法插桩)visitEnd
表示类访问结束(如果是Activity,访问结束了还没有onWindowFocusChanged方法则插入并加上AppMethodBeat.at()方法
)。
TraceMethodAdapter
private class TraceMethodAdapter extends AdviceAdapter {
@Override
protected void onMethodEnter() {
//在方法入口处插入AppMethodBeat.i()
TraceMethod traceMethod = collectedMethodMap.get(methodName);
if (traceMethod != null) {
traceMethodCount.incrementAndGet();
mv.visitLdcInsn(traceMethod.id);
mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
if (checkNeedTraceWindowFocusChangeMethod(traceMethod)) {
//有onWindowFocusChanged方法则直接插入AppMethodBeat.at
traceWindowFocusChangeMethod(mv, className);
}
}
}
@Override
protected void onMethodExit(int opcode) {
//在方法出口插入AppMethodBeat.o()
TraceMethod traceMethod = collectedMethodMap.get(methodName);
if (traceMethod != null) {
traceMethodCount.incrementAndGet();
mv.visitLdcInsn(traceMethod.id);
mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
}
}
}
小结
插件的实现类为MatrixPlugin
,插桩任务在编译期间执行,混淆后插桩。 Transform
主要分为三步:
1. 解析 : 根据mapping.txt
和配置的blackMethodList.txt
解析得到类、方法在混淆前和混淆后的映射关系,保存在MappingCollector中。
2. 收集 : 将方法信息封装成TraceMethod,把需要插桩和不需要插桩(过滤黑名单、空方法、get/set等简单方法)的方法存入两个map
3. 插桩 : 使用ASM在方法入口插入AppMethodBeat.i
,方法出口插入AppMethodBeat.o
,Activity的onWindowFocusChanged插入AppMethodBeat.at
。
APM框架Matrix源码分析——字节码插桩之扩展网络监控
本篇文章主要介绍如何基于ASM + OkHttp3 + 自定义拦截器
来实现网络请求的监控
1.插桩位置
我们需要我们自定义监控网络Interceptor插入到合适的位置。先来分析下拦截器的处理流程:
//RealCall.java
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//自定义拦截器
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
//链条对象Chain
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
//执行下一个拦截器
return chain.proceed(originalRequest);
}
通过五大拦截器完成整个请求过程,责任链模式—> Request请求,经过5个拦截器,发送到最下边返回响应Response,再发送到最上边。这样自定义的拦截器既可以拿到请求数据,又可以拿到响应数据。
我们需要把自定义的拦截器插到client.interceptors()
public class OkHttpClient implements Cloneable, Call.Factory {
//OkHttpClient中的成员变量interceptors,用来存放自定义拦截器
final List<Interceptor> interceptors;
//这个就是上述client.interceptors(),是OkHttpClient中的成员变量interceptors
public List<Interceptor> interceptors() {
return interceptors;
}
//OkHttpClient构造函数
public OkHttpClient() {
this(new Builder());
}
private OkHttpClient(Builder builder) {
//传入了Builder的成员变量interceptors
this.interceptors = Util.immutableList(builder.interceptors);
//...
}
public static final class Builder {
//Builder的成员变量interceptors
final List<Interceptor> interceptors = new ArrayList<>();
public Builder() {
//...
//插桩点:往interceptors中添加自定义拦截器
}
Builder(OkHttpClient okHttpClient) {
//...
//插桩点:往interceptors中添加自定义拦截器
}
}
}
client.interceptors()
方法返回了OkHttpClient中的成员变量interceptors(用来存放自定义拦截器),OkHttpClient构造的时候被传入了Builder的成员变量interceptors,所有我们只需要在Builder的两个构造函数的最后一行把自定义拦截器添加到Builder的成员变量interceptors即可。
2.自定义监控网络Interceptor
public class NetWorkInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
//记录开始时间
long startNs = System.currentTimeMillis();
//从链表缓冲池中取OkHttpDataReplay(存每个网络请求的信息),没有则new一个
replay = OkHttpDataReplay.create()
//记录开始时间
replay.startNs = startNs;
Request request = chain.request();
//记录请求数据,如url、上行数据大小等
recordRequest(request);
Response response;
try {
response = chain.proceed(request);
} catch (IOException e) {
throw e;
}
//记录耗时
replay.costTime = System.currentTimeMillis() - startNs;
//记录响应数据,如响应码,下行数据大小等
recordResponse(response);
//收集数据分析
doReplayCopy(replay);
//处理完成回收,用于复用
replay.recycle();
}
}
通过自定义拦截器的方式,记录请求数据和响应数据。
为了减少字节码操作,定义一个工具类协助AMS进行代码插入,传入Builder的成员变量interceptors,判断自定义监控网络Interceptor是否已经添加过,没有则添加:
public class OkHttpUtils {
public static void insertToOkHttpClientBuilder(List<Interceptor> interceptors) {
try {
//判断是否插入过自定义监控网络Interceptor
boolean hasAddNetWorkInterceptor = false;
for (Interceptor interceptor : interceptors) {
if (interceptor instanceof NetWorkInterceptor) {
hasAddNetWorkInterceptor = true;
break;
}
}
//没有则插入,避免重复
if (!hasAddNetWorkInterceptor) {
interceptors.add(new NetWorkInterceptor());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.插桩
先要判断当前访问的类是不是OkHttpClient的内部类Builder,接着使用Okhttp3MethodAdapter去处理插桩
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
//判断当前访问的类是不是OkHttpClient的内部类Builder
if ("okhttp3/OkHttpClient\$Builder".equals(className)) {
//返回一个Okhttp3MethodAdapter去处理插桩
return new Okhttp3MethodAdapter(name, api, access, desc, methodVisitor);
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
Okhttp3MethodAdapter
class Okhttp3MethodAdapter(private val methodName: String, api: Int, access: Int, private val desc: String, mv: MethodVisitor?) : LocalVariablesSorter(api, access, desc, mv) {
override fun visitInsn(opcode: Int) {
//在okhttp3.OkHttpClient$Builder构造函数的最后一行插入insertToOkHttpClientBuilder
if (isReturn(opcode) && isOkhttpClientBuild(methodName, desc)) {
//加载this
mv.visitVarInsn(ALOAD, 0)
//访问Builder的interceptors
mv.visitFieldInsn(GETFIELD, "okhttp3/OkHttpClient\$Builder", "interceptors", "Ljava/util/List;")
//调用OkHttpUtils的静态方法insertToOkHttpClientBuilder
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/tencent/matrix/trace/okhttp3/OkHttpUtils", "insertToOkHttpClientBuilder", "(Ljava/util/List;)V", false)
}
super.visitInsn(opcode)
}
private fun isReturn(opcode: Int): Boolean {
return ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW)
}
private fun isOkhttpClientBuild(methodName: String, methodDesc: String): Boolean {
return ("<init>" == methodName && ("()V" == methodDesc || "(Lokhttp3/OkHttpClient;)V" == methodDesc))
}
}
也可以继承AdviceAdapter在方法出口onMethodExit插入