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

2,704 阅读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还有几个要素,即实现其getInputTypesgetOutputTypesgetScopesgetNameisIncremental以及最重要的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.txtblackMethodList.txtbaseMethodMapFile)分析方法统计规则,比如混淆后的类名和原始类名之间的映射关系、不需要插桩的方法黑名单等
  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 中指定的方法不需要统计。

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