背景
对于APM项目,诸如像 Debug 日志,运行耗时监控等都会陆陆续续加入到源码中,随着功能的增多,这些监控日志代码在某种程度上会影响甚至是干扰业务代码的阅读,Matrix如何做到自动化在代码中自动插入方法呢?
“插桩”就映入眼帘了,本质的思想都是 AOP,在编译或运行时动态注入代码。如下是app的包流程
在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插件有两个功能模块:
trace:给每个需要插桩的方法分配唯一的方法id,并在方法的进出口插入一段代码,为TraceCanary模块分析实际问题提供数据支撑。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文件,每个文件指定自己的实现类即可。
2. MatrixPlugin
自定义的插件需要实现了Plugin接口,并在apply方法里面完成要做的事情。
在MatrixPlugin中干了两件事。
- 首先是在项目的配置阶段通过
project.extensions.create(name, type)方法将插件的自定义配置项以对应的type创建并保存起来,之后可以通过project.name获取到对应的配置项。 - 其次在项目配置完毕的回调
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,主要做了三件事:
- 添加
Extension,用于提供给用户自定义配置选项 - 读取
extension配置,如果启用trace功能,则执行MatrixTraceTransform,统计方法并插桩 - 读取
extension配置,如果启用removeUnusedResources功能,则执行RemoveUnusedResourcesTask,删除不需要的资源
需要注意的是,插桩任务是在编译期执行的,这是为了避免对混淆操作产生影响。因为 proguard 操作是在该任务之前就完成的,意味着插桩时的 class 文件已经被混淆过的。而选择 proguard 之后去插桩,是因为如果提前插桩会造成部分方法不符合内联规则,没法在proguard 时进行优化,最终导致程序方法数无法减少,从而引发方法数过大问题transform主要分三步执行:
- 根据配置文件(
mapping.txt、blackMethodList.txt、baseMethodMapFile)分析方法统计规则,比如混淆后的类名和原始类名之间的映射关系、不需要插桩的方法黑名单等 - 借助 ASM 访问所有
Class文件的方法,记录其 ID,并写入到文件中(methodMapping.txt) - 插桩
插桩处理流程主要包含四步:
- 进入方法时执行
AppMethodBeat.i,传入方法 ID,记录时间戳 - 退出方法时执行
AppMethodBeat.o,传入方法 ID,记录时间戳 - 如果是
Activity,并且没有onWindowFocusChanged方法,则插入该方法 - 跟踪
onWindowFocusChanged方法,退出时执行AppMethodBeat.at,计算启动耗时
值得注意的细节有:
- 统计的方法包括应用自身的、JAR 依赖包中的,以及额外添加的 ID 固定的
dispatchMessage方法 - 抽象类或接口类不需要统计
- 空方法、
get & set方法等简单方法不需要统计 blackMethodList.txt中指定的方法不需要统计。
关于资源的插桩逻辑后续清楚包的资源知识后再分享