布景
关于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
中指定的办法不需求计算。
关于资源的插桩逻辑后续清楚包的资源常识后再共享