APM框架Matrix源码剖析(七)字节码插桩
插件进口
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
。