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类文件中的字节码

  1. visit办法开端拜访类文件时被调用(做了一些根本判别,是否是Activity,是否需求插桩,判别抽象类和接口)
  2. visitMethod用于拜访办法信息(是否是onWindowFocusChanged办法,过滤了抽象类和接口,办法插桩)
  3. 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