腾讯性能监控框架Matrix源码分析(十二)插桩 之MatrixTrace

2,998 阅读11分钟
背景

对于APM项目,诸如像 Debug 日志,运行耗时监控等都会陆陆续续加入到源码中,随着功能的增多,这些监控日志代码在某种程度上会影响甚至是干扰业务代码的阅读,Matrix如何做到自动化在代码中自动插入方法呢?

“插桩”就映入眼帘了,本质的思想都是 AOP,在编译或运行时动态注入代码。如下是app的包流程

16a5d198d39bb3f0~tplv-t2oaga2asx-zoom-in-crop-mark-4536-0-0-0.image.png

在Transform的过程中,从java到class,class到dex,dex到apk我们都可以hook作插桩操作,Matrix这里是从编译后的class 字节码下手,结合更高效的操作库ASM完成的。

很幸运,Google 官方在 Android Gradle 的 1.5.0 版本以后提供了 Transfrom API, 允许第三方自定义插件在打包 dex 文件之前的编译过程中操作 .class 文件,所以这里先要做的就是实现一个自定义的 Transform 进行.class文件遍历拿到所有方法,修改完成对原文件进行替换。这个功能叫做Android Gradle Plugin,简称AGP,具体相关知识大家可以自行百度,下面开始源码分析

Matrix-ASM插桩插件解析

matrix-plugin插件有两个功能模块:

  1. trace:给每个需要插桩的方法分配唯一的方法id,并在方法的进出口插入一段代码,为TraceCanary模块分析实际问题提供数据支撑。
  2. removeUnusedResources:在合成apk之前移除apkchecker检测出来的没有用到的资源清单,可以自动化的减少最终包体积大小。

1. AGP的入口

程序都有main方法作为程序的入口方法,那么Android Gradle Plugin(AGP)的入口在哪里呢。 

其实AGP的入口文件也比较固定,位于src/main/resources/META-INF/gradle-plugins目录下。在matrix-gradle-plugin的对应目录下我们发现了一个文件:com.tencent.matrix-plugin.properties。
.properties是文件的后缀名,因此这个文件的名称就是com.tencent.matrix-plugin。我们在应用插件的时候,填上这个名字就行了。

我们在sample中印证一下,看看在sample中是如何应用matrix-plugin的:apply plugin: 'com.tencent.matrix-plugin'。

*.properties里面的写法也很固定:implementation-class=com.tencent.matrix.plugin.MatrixPlugin。这表示了这个Plugin真正的实现类是com.tencent.matrix.plugin.MatrixPlugin。

因此,我们可以直奔MatrixPlugin类看里面的实现了

有些库会提供多个插件,实现上只需要在src/main/resources/META-INF/gradle-plugins目录下放多个.properties文件,每个文件指定自己的实现类即可。

图片.png

2. MatrixPlugin

自定义的插件需要实现了Plugin接口,并在apply方法里面完成要做的事情。

在MatrixPlugin中干了两件事。

  1. 首先是在项目的配置阶段通过project.extensions.create(name, type)方法将插件的自定义配置项以对应的type创建并保存起来,之后可以通过project.name获取到对应的配置项。
  2. 其次在项目配置完毕的回调project.afterEvaluate(这个回调会在tasks执行之前进行执行)中,将要执行任务的插入到task链中并设置依赖关系。这样随着构建任务的一个个执行,会执行到我们的代码。
    对MatrixPlugin的两个子功能模块来说,这一步实现的方式有一点区别。trace模块因为是对所有有效的方法进行插桩,需要在proguard等任务完成之后在执行,而这个时序不太好通过依赖关系进行确定,因此选择了hook了class打包成dex的这一过程,最终达到了先插桩后打dex的目的。而removeUnusedResources只需要在将所有资源打包成apk之前执行即可。这两个子模块将会分开讨论。
class MatrixPlugin : Plugin<Project> {
    companion object {
        const val TAG = "Matrix.Plugin"
    }

    override fun apply(project: Project) {
        // 创建并保存自定义配置项
        val matrix = project.extensions.create("matrix", MatrixExtension::class.java)
         //方法功能插桩
        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 {
            Log.setLogLevel(matrix.logLevel)
        }

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

跟进MatrixTasksManager().createMatrixTasks()

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

    createMatrixTraceTask(android, project, traceExtension)

    createRemoveUnusedResourcesTask(android, project, removeUnusedResourcesExtension)
}

createRemoveUnusedResourcesTask

private fun createRemoveUnusedResourcesTask(
        android: AppExtension,
        project: Project,
        removeUnusedResourcesExtension: MatrixRemoveUnusedResExtension) {

    project.afterEvaluate {

        if (!removeUnusedResourcesExtension.enable) {
            return@afterEvaluate
        }

        android.applicationVariants.all { variant ->
            if (Util.isNullOrNil(removeUnusedResourcesExtension.variant) ||
                    variant.name.equals(removeUnusedResourcesExtension.variant, true)) {
                Log.i(TAG, "RemoveUnusedResourcesExtension: %s", removeUnusedResourcesExtension)
                //兼容v1 v2
                val removeUnusedResourcesTaskProvider = if (removeUnusedResourcesExtension.v2) {
                    val action = RemoveUnusedResourcesTaskV2.CreationAction(
                            CreationConfig(variant, project), removeUnusedResourcesExtension
                    )
                    project.tasks.register(action.name, action.type, action)
                } else {
                    val action = RemoveUnusedResourcesTask.CreationAction(
                            CreationConfig(variant, project), removeUnusedResourcesExtension
                    )
                    project.tasks.register(action.name, action.type, action)
                }

                // assembleProvider依赖于removeUnusedResourcesTaskProvider,即assembleProvider依赖于removeUnusedResourcesTaskProvider先执行
                variant.assembleProvider?.configure {
                    it.dependsOn(removeUnusedResourcesTaskProvider)
                }

                // removeUnusedResourcesTaskProvider依赖于packageApplicationProvider,即removeUnusedResourcesTaskProvider先执行
                removeUnusedResourcesTaskProvider.configure {
                    it.dependsOn(variant.packageApplicationProvider)
                }
                // 也就是说,执行顺序为packageApplicationProvider -> removeUnusedResourcesTaskProvider -> assemble
            }
        }
    }
}

执行顺序为packageApplicationProvider -> removeUnusedResourcesTaskProvider -> assemble

createMatrixTraceTask()

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

根据不同版本的AGP和功能进行了相应的适配,最终都会走到统一的处理MatrixTrace

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) -> {
            if (project.extensions.extraProperties.has(LEGACY_FLAG) &&
                (project.extensions.extraProperties.get(LEGACY_FLAG) as? String?) == "true") {
                legacyInject(appExtension, project, extension)
            } else {
                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)
        }
    }
}

3. MatrixTraceTransform

上面MatrixPlugin的代码中可以看到,对于trace模块调用了MatrixTraceTransform#inject方法。 

在该方法中会遍历task,找到指定名称的task,替换里面的transform对象为MatrixTraceTransform对象。

class MatrixTraceLegacyTransform(
        private val project: Project,
        private val config: Configuration,
        private val origTransform: Transform
) : Transform() {

    companion object {
        const val TAG = "Matrix.TraceLegacyTransform"

        fun inject(extension: MatrixTraceExtension, project: Project, variant: BaseVariant) {
            //初始化配置文件
            val mappingOut = Joiner.on(File.separatorChar).join(
                    project.buildDir.absolutePath,
                    FD_OUTPUTS,
                    "mapping",
                    variant.dirName)

            val traceClassOut = Joiner.on(File.separatorChar).join(
                    project.buildDir.absolutePath,
                    FD_OUTPUTS,
                    "traceClassOut",
                    variant.dirName)

            val config = Configuration.Builder()
                    .setPackageName(variant.applicationId)
                    .setBaseMethodMap(extension.baseMethodMapFile)
                    .setBlockListFile(extension.blackListFile)
                    .setMethodMapFilePath("$mappingOut/methodMapping.txt")
                    .setIgnoreMethodMapFilePath("$mappingOut/ignoreMethodMapping.txt")
                    .setMappingPath(mappingOut)
                    .setTraceClassOut(traceClassOut)
                    .setSkipCheckClass(extension.isSkipCheckClass)
                    .build()

            val hardTask = getTransformTaskName(extension.customDexTransformName, variant.name)
            //在该方法中会遍历task,找到指定名称的task,替换里面的transform对象为`MatrixTraceLegacyTransform`对象。
            for (task in project.tasks) {
                for (str in hardTask) {
                    if (task.name.equals(str, ignoreCase = true) && task is TransformTask) {
                        Log.i(TAG, "successfully inject task:" + task.name)
                        //反射搞起来
                        val field = TransformTask::class.java.getDeclaredField("transform")
                        field.isAccessible = true
                        field.set(task, MatrixTraceLegacyTransform(project, config, task.transform))
                        break
                    }
                }
            }
        }
private fun getTransformTaskName(customDexTransformName: String?, buildTypeSuffix: String): Array<String> {
    return if (!Util.isNullOrNil(customDexTransformName)) {
        arrayOf(
                "${customDexTransformName}For$buildTypeSuffix"
        )
    } else {
        arrayOf(
                "transformClassesWithDexBuilderFor$buildTypeSuffix",
                "transformClassesWithDexFor$buildTypeSuffix"
        )
    }
}

extension.getCustomDexTransformName()一般没有配置,以release版本为例,所以最终要hook的task为transformClassesWithDexBuilderForRelease以及transformClassesWithDexForRelease,对应的transform为DexTransform。

此外,MatrixTraceLegacyTransform还有几个要素,即实现其getInputTypes、getOutputTypes、getScopes、getName、isIncremental以及最重要的transform方法。换句话说,自定义transform需要指定什么范围的什么输入,经过怎么样的transform,最后输出什么。

override fun getName(): String {
    return TAG
}

override fun getInputTypes(): Set<QualifiedContent.ContentType> {
    return TransformManager.CONTENT_CLASS
}

override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
    return TransformManager.SCOPE_FULL_PROJECT
}

override fun isIncremental(): Boolean {
    return true
}

之后走到transform

override fun transform(transformInvocation: TransformInvocation) {
    super.transform(transformInvocation);
    val start = System.currentTimeMillis();
    try {
        //自定义插桩逻辑
        doTransform(transformInvocation); // hack
    } catch (e: Throwable) {
        e.printStackTrace()
    }
    val cost = System.currentTimeMillis() - start;
    val begin = System.currentTimeMillis();
    //系统原始的操作
    origTransform.transform(transformInvocation);
    val origTransformCost = System.currentTimeMillis() - begin;
    Log.i("Matrix.$name", "[transform] cost time: %dms %s:%sms MatrixTraceTransform:%sms",
            System.currentTimeMillis() - start, origTransform.javaClass.simpleName, origTransformCost, cost);
}

进入doTransform

private fun doTransform(invocation: TransformInvocation) {

    val start = System.currentTimeMillis()

    val isIncremental = invocation.isIncremental && this.isIncremental
    //初始化容器,合并源码和Jar依赖文件
    val changedFiles = ConcurrentHashMap<File, Status>()
    val inputFiles = ArrayList<File>()

    val fileToInput = ConcurrentHashMap<File, QualifiedContent>()
    
    for (input in invocation.inputs) {
        //源码依赖文件
        for (directoryInput in input.directoryInputs) {
            changedFiles.putAll(directoryInput.changedFiles)
            inputFiles.add(directoryInput.file)
            fileToInput[directoryInput.file] = directoryInput
        }
        //jar引用文件
        for (jarInput in input.jarInputs) {
            changedFiles[jarInput.file] = jarInput.status
            inputFiles.add(jarInput.file)
            fileToInput[jarInput.file] = jarInput
        }
    }

    if (inputFiles.size == 0) {
        Log.i(TAG, "Matrix trace do not find any input files")
        return
    }

    val legacyReplaceChangedFile = { inputDir: File, map: Map<File, Status> ->
        replaceChangedFile(fileToInput[inputDir] as DirectoryInput, map)
        inputDir as Object
    }

    var legacyReplaceFile = { input: File, output: File ->
        replaceFile(fileToInput[input] as QualifiedContent, output)
        input as Object
    }

    MatrixTrace(
            ignoreMethodMapFilePath = config.ignoreMethodMapFilePath,
            methodMapFilePath = config.methodMapFilePath,
            baseMethodMapPath = config.baseMethodMapPath,
            blockListFilePath = config.blockListFilePath,
            mappingDir = config.mappingDir,
            project = project
    ).doTransform(
            classInputs = inputFiles,
            changedFiles = changedFiles,
            isIncremental = isIncremental,
            skipCheckClass = config.skipCheckClass,
            traceClassDirectoryOutput = File(config.traceClassOut),
            inputToOutput = ConcurrentHashMap(),
            legacyReplaceChangedFile = legacyReplaceChangedFile,
            legacyReplaceFile = legacyReplaceFile,
            uniqueOutputName = true
    )

    val cost = System.currentTimeMillis() - start
    Log.i(TAG, " Insert matrix trace instrumentations cost time: %sms.", cost)
}

最终到了最核心的类MatrixTrace

doTransform 分为3步,我们一步一步介绍。

第一步
var start = System.currentTimeMillis()

val futures = LinkedList<Future<*>>()

val mappingCollector = MappingCollector()
val methodId = AtomicInteger(0)
val collectedMethodMap = ConcurrentHashMap<String, TraceMethod>()
//处理混淆和黑名单
futures.add(executor.submit(ParseMappingTask(
        mappingCollector, collectedMethodMap, methodId, config)))


// 储存 class 输入输出关系的
val dirInputOutMap = ConcurrentHashMap<File, File>()
// 储存 jar 输入输出关系的
val jarInputOutMap = ConcurrentHashMap<File, File>()
// 处理输入
// 主要是将输入类的字段替换掉,替换到指定的输出位置
// 里面做了增量的处理
for (file in classInputs) {
    if (file.isDirectory) {
        futures.add(executor.submit(CollectDirectoryInputTask(
                directoryInput = file,
                mapOfChangedFiles = changedFiles,
                mapOfInputToOutput = inputToOutput,
                isIncremental = isIncremental,
                traceClassDirectoryOutput = traceClassDirectoryOutput,
                legacyReplaceChangedFile = legacyReplaceChangedFile,
                legacyReplaceFile = legacyReplaceFile,

                // result
                resultOfDirInputToOut = dirInputOutMap
        )))
    } else {
        val status = Status.CHANGED
        futures.add(executor.submit(CollectJarInputTask(
                inputJar = file,
                inputJarStatus = status,
                inputToOutput = inputToOutput,
                isIncremental = isIncremental,
                traceClassFileOutput = traceClassDirectoryOutput,
                legacyReplaceFile = legacyReplaceFile,
                uniqueOutputName = uniqueOutputName,

                // result
                resultOfDirInputToOut = dirInputOutMap,
                resultOfJarInputToOut = jarInputOutMap
        )))
    }
}

for (future in futures) {
    future.get()
}
futures.clear()

解析 Proguard mapping file 解析黑名单 加载已分配 method id 的函数列表

class ParseMappingTask
constructor(
        private val mappingCollector: MappingCollector,
        private val collectedMethodMap: ConcurrentHashMap<String, TraceMethod>,
        private val methodId: AtomicInteger,
        private val config: Configuration
) : Runnable {

    override fun run() {
        val start = System.currentTimeMillis()
        // 解析 Proguard mapping file
        val mappingFile = File(config.mappingDir, "mapping.txt")
        if (mappingFile.isFile) {
            val mappingReader = MappingReader(mappingFile)
            mappingReader.read(mappingCollector)
        }
        // 解析黑名单
        val size = config.parseBlockFile(mappingCollector)

        val baseMethodMapFile = File(config.baseMethodMapPath)
        // 加载已分配 method id 的函数列表
        getMethodFromBaseMethod(baseMethodMapFile, collectedMethodMap)
        retraceMethodMap(mappingCollector, collectedMethodMap)

        Log.i(TAG, "[ParseMappingTask#run] cost:%sms, black size:%s, collect %s method from %s",
                System.currentTimeMillis() - start, size, collectedMethodMap.size, config.baseMethodMapPath)
    }

创建 ParseMappingTask,读取 mapping 文件,因为这个时候 class 已经被混淆了,之所以选在混淆后,是因为避免插桩导致某些编译器优化失效,等编译器优化完了再插桩。读取 mapping 文件有几个用处,第一,需要输出某些信息,肯定不能输出混淆后的class信息。第二,配置文件的类是没有混淆过的,读取进来需要能够转换为混淆后的,才能处理。

创建了两个 map,储存 class jar 文件的输入输出位置。 创建 CollectDirectoryInputTask,收集 class 文件到 map。 创建 CollectJarInputTask,收集 jar 文件到 map。

CollectDirectoryInputTask 与 CollectJarInputTask 里面还用到了反射,更改了其输出目录到 build/output/traceClassout。所以我们可以在这里看到插桩后的类。这两个类就做了这些事,就不贴代码了。

后面就是调用 future 的 get 方法,等待这里 task 执行完成,再进行下一步。

第二步
/**
 * step 2
 */
start = System.currentTimeMillis()
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 {
    List<Future> futures = new LinkedList<>();
    // 将 文件/目录下 class ,全部放到 list 中
    for (File srcFile : srcFolderList) {
        ArrayList<File> classFileList = new ArrayList<>();
        if (srcFile.isDirectory()) {
            listClassFiles(classFileList, srcFile);
        } else {
            classFileList.add(srcFile);
        }
    // 每个 class 都分配一个 CollectSrcTask
        for (File classFile : classFileList) {
            futures.add(executor.submit(new CollectSrcTask(classFile)));
        }
    }
    // 每个 jar 都分配一个 CollectSrcTask
    for (File jarFile : dependencyJarList) {
        futures.add(executor.submit(new CollectJarTask(jarFile)));
    }
    //等待任务完成
    for (Future future : futures) {
        future.get();
    }
    futures.clear();

    futures.add(executor.submit(new Runnable() {
        @Override
        public void run() {
         // 将被插桩的方法名存入ignoreMethodMapping.txt 中
            saveIgnoreCollectedMethod(mappingCollector);
        }
    }));

    futures.add(executor.submit(new Runnable() {
        @Override
        public void run() {
         // 将被插桩的 方法名 存入 methodMapping.txt 中
            saveCollectedMethod(mappingCollector);
        }
    }));

    for (Future future : futures) {
        future.get();
    }
    futures.clear();

}

CollectSrcTask和CollectJarTask差不多,我们看其中一个

@Override
public void run() {
    ...
        is = new FileInputStream(classFile);
        // ASM 的使用
        // 访问者模式,就是将对数据结构访问的操作分离出去
        // 代价就是需要将数据结构本身传递进来
        ClassReader classReader = new ClassReader(is);
        // 修改字节码,有时候需要改动本地变量数与stack大小,自己计算麻烦,可以直接使用这个自动计算
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        // ASM5 api版本
        ClassVisitor visitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
        classReader.accept(visitor, 0);
     ...

}

最后走到,TraceClassAdapter:

@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;
    }
    // 保存父类,便于分析继承关系
    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 {
        if (!hasWindowFocusMethod) {
            // 是否有 onWindowFocusChanged 方法,针对 activity 的
            hasWindowFocusMethod = isWindowFocusChangeMethod(name, desc);
        }
        return new CollectMethodNode(className, access, name, desc, signature, exceptions);
    }
}

这里面没有主要逻辑,主要逻辑在 CollectMethodNode:

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

    // 是否构造方法
    if ("<init>".equals(name)) {
        isConstructor = true;
    }

    // 判断类是否 被配置在了 黑名单中
    boolean isNeedTrace = isNeedTrace(configuration, traceMethod.className, mappingCollector);
    //为了减少插桩量及性能损耗,通过遍历 `class` 方法指令集,判断扫描的函数是否只含有 `PUT/READ FIELD` 等简单的指令,来过滤一些默认或匿名构造函数,以及 `get/set` 等简单不耗时函数
    if ((isEmptyMethod() || isGetSetMethod() || isSingleMethod())
            && isNeedTrace) {
        ignoreCount.incrementAndGet();
        // 存入 ignore map
        collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
        return;
    }

    // 不在黑名单中
    if (isNeedTrace && !collectedMethodMap.containsKey(traceMethod.getMethodName())) {
        traceMethod.id = methodId.incrementAndGet();
        // 存入 map
        collectedMethodMap.put(traceMethod.getMethodName(), traceMethod);
        incrementCount.incrementAndGet();
    } else if (!isNeedTrace && !collectedIgnoreMethodMap.containsKey(traceMethod.className)) {
        ignoreCount.incrementAndGet();
        // 存入 ignore map
        collectedIgnoreMethodMap.put(traceMethod.getMethodName(), traceMethod);
    }

}

可以看到,最终是将 class 中满足条件的方法,都存入到了 collectedMethodMap,忽略的方法存入了 collectedIgnoreMethodMap。

第三步
/**
 * step 3
 */
start = System.currentTimeMillis()
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)

子线程执行

public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList, ClassLoader classLoader, boolean ignoreCheckClass) throws ExecutionException, InterruptedException {
    List<Future> futures = new LinkedList<>();
    traceMethodFromSrc(srcFolderList, futures, classLoader, ignoreCheckClass);
    traceMethodFromJar(dependencyJarList, futures, classLoader, ignoreCheckClass);
    for (Future future : futures) {
        future.get();
    }
    if (traceError) {
        throw new IllegalArgumentException("something wrong with trace, see detail log before");
    }
    futures.clear();
}

逻辑差不多,这块看src

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);
                }
            }));
        }
    }
}

进行文件的真正插桩操作 核心的改动在TraceClassAdapter

private void innerTraceMethodFromSrc(File input, File output, ClassLoader classLoader, boolean ignoreCheckClass) {

    ArrayList<File> classFileList = new ArrayList<>();
    if (input.isDirectory()) {
        listClassFiles(classFileList, input);
    } else {
        classFileList.add(input);
    }

    for (File classFile : classFileList) {
        InputStream is = null;
        FileOutputStream os = null;
        try {
            final String changedFileInputFullPath = classFile.getAbsolutePath();
            final File changedFileOutput = new File(changedFileInputFullPath.replace(input.getAbsolutePath(), output.getAbsolutePath()));

            if (changedFileOutput.getCanonicalPath().equals(classFile.getCanonicalPath())) {
                throw new RuntimeException("Input file(" + classFile.getCanonicalPath() + ") should not be same with output!");
            }

            if (!changedFileOutput.exists()) {
                changedFileOutput.getParentFile().mkdirs();
            }
            changedFileOutput.createNewFile();

            if (MethodCollector.isNeedTraceFile(classFile.getName())) {

                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;
                    }
                }

                if (output.isDirectory()) {
                    os = new FileOutputStream(changedFileOutput);
                } else {
                    os = new FileOutputStream(output);
                }
                os.write(data);
                os.close();
            } else {
                FileUtil.copyFileUsingStream(classFile, changedFileOutput);
            }
        } catch (Exception e) {
            Log.e(TAG, "[innerTraceMethodFromSrc] input:%s e:%s", input.getName(), e.getMessage());
            try {
                Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        } finally {
            try {
                is.close();
                os.close();
            } catch (Exception e) {
                // ignore
            }
        }
    }
}

进入TraceClassAdapter

private class TraceClassAdapter extends ClassVisitor {

    private String className;
    private String superName;
    private boolean isABSClass = false;
    private boolean hasWindowFocusMethod = false;
    private boolean isActivityOrSubClass;
    private boolean isNeedTrace;

    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;
        this.superName = superName;
        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;
        }

    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
                                     String signature, String[] exceptions) {
        //是否有获取焦点方法论
        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() {
       //针对界面启动耗时,因为要统计从 `Activity.onCreate` 到
       //Ativity.onWindowFocusChange` 间的耗时,
       //在插桩过程中需要收集应用内所有 `Activity` 的实现类,
       //覆盖`onWindowFocusChange` 函数进行打点
        if (!hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
            //如果 Activity 没有覆盖 onWindowFocusChanged 则覆盖之
            insertWindowFocusChangeMethod(cv, className, superName);
        }
        super.visitEnd();
    }
}

如果类没有遇到onWindowFocusChanged方法且是Activity或子类且需要插桩,则使用ASM API插入这么一段代码:

private void insertWindowFocusChangeMethod(ClassVisitor cv, String classname) {
    // public void onWindowFocusChanged (boolean)
    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();
    // this
    methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
    // boolean
    methodVisitor.visitVarInsn(Opcodes.ILOAD, 1);
    // super.onWindowFocusChanged(boolean)
    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);
    // com/tencent/matrix/trace/core/AppMethodBeat.at(this, boolean)
    traceWindowFocusChangeMethod(methodVisitor, classname);
    // 返回语句
    methodVisitor.visitInsn(Opcodes.RETURN);
    methodVisitor.visitMaxs(2, 2);
    methodVisitor.visitEnd();
}

上面这段代码可能看着头疼,因为这涉及到了字节码的层面。不过也不用太担心,我们可以在AS上下载ASM Bytecode Viewer插件,先写好要插桩的代码,然后使用此插件查看ASM的对应写法,可以增加效率。 方法插桩最终走到TraceMethodAdapter

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

}

@Override
protected void onMethodEnter() {
    TraceMethod traceMethod = collectedMethodMap.get(methodName);
    if (traceMethod != null) {
        traceMethodCount.incrementAndGet();
        mv.visitLdcInsn(traceMethod.id);
        // 插入 void com/tencent/matrix/trace/core/AppMethodBeat.i(int)
        mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
        if (checkNeedTraceWindowFocusChangeMethod(traceMethod)) {
            traceWindowFocusChangeMethod(mv, className);
        }
    }
}
 

@Override
protected void onMethodExit(int opcode) {
    TraceMethod traceMethod = collectedMethodMap.get(methodName);
    if (traceMethod != null) {
        traceMethodCount.incrementAndGet();
        mv.visitLdcInsn(traceMethod.id);
        // 插入 void com/tencent/matrix/trace/core/AppMethodBeat.o(int)
        mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
    }
}

private void traceWindowFocusChangeMethod(MethodVisitor mv, String classname) {
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    mv.visitVarInsn(Opcodes.ILOAD, 1);
    mv.visitMethodInsn(Opcodes.INVOKESTATIC, 
    // com/tencent/matrix/trace/core/AppMethodBeat.at(this, boolean)
    TraceBuildConstants.MATRIX_TRACE_CLASS, "at", "(Landroid/app/Activity;Z)V", false);
}

private void insertWindowFocusChangeMethod(ClassVisitor cv, String classname, String superClassName) {
    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, superClassName, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD,
            TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS, false);
    traceWindowFocusChangeMethod(methodVisitor, classname);
    methodVisitor.visitInsn(Opcodes.RETURN);
    methodVisitor.visitMaxs(2, 2);
    methodVisitor.visitEnd();

}

总结

Matrix 的 Gradle 插件的实现类为 MatrixPlugin,主要做了三件事:

  1. 添加 Extension,用于提供给用户自定义配置选项
  2. 读取 extension 配置,如果启用 trace 功能,则执行 MatrixTraceTransform,统计方法并插桩
  3. 读取 extension 配置,如果启用 removeUnusedResources 功能,则执行RemoveUnusedResourcesTask,删除不需要的资源

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

  1. 根据配置文件(mapping.txt、blackMethodList.txt、baseMethodMapFile)分析方法统计规则,比如混淆后的类名和原始类名之间的映射关系、不需要插桩的方法黑名单等
  2. 借助 ASM 访问所有 Class 文件的方法,记录其 ID,并写入到文件中(methodMapping.txt)
  3. 插桩

插桩处理流程主要包含四步:

  1. 进入方法时执行 AppMethodBeat.i,传入方法 ID,记录时间戳
  2. 退出方法时执行 AppMethodBeat.o,传入方法 ID,记录时间戳
  3. 如果是 Activity,并且没有 onWindowFocusChanged 方法,则插入该方法
  4. 跟踪 onWindowFocusChanged 方法,退出时执行 AppMethodBeat.at,计算启动耗时

值得注意的细节有:

  1. 统计的方法包括应用自身的、JAR 依赖包中的,以及额外添加的 ID 固定的 dispatchMessage 方法
  2. 抽象类或接口类不需要统计
  3. 空方法、get & set 方法等简单方法不需要统计
  4. blackMethodList.txt 中指定的方法不需要统计。

关于资源的插桩逻辑后续清楚包的资源知识后再分享