ArgusAPMTransform
ArgusAPMTransform这个类继承自Transform。这个类的主要函数如下:
override fun getInputTypes(): Set<QualifiedContent.ContentType> {
return Sets.immutableEnumSet(QualifiedContent.DefaultContentType.CLASSES)!!
}
override fun isIncremental(): Boolean {
return true
}
override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
return TransformManager.SCOPE_FULL_PROJECT
}
override fun transform(transformInvocation: TransformInvocation){...}
getInputTypes() 用于指明Transform的输入类型,可以作为输入过滤的手段 。如果子类没有复写的话,默认返回值等于Transform#getInputTypes()。 isIncremental() 用于表示是否支持增量编译。getScopes用于指明Transform的作用域,具体的类型在TransformManager定义,接下来就是Transform类型task的重点transform,其代码如下:
override fun transform(transformInvocation: TransformInvocation) {
asmWeaver = ASMWeaver()
if (!transformInvocation.isIncremental) {
transformInvocation.outputProvider.deleteAll()
}
transformInvocation.inputs.forEach { input ->
input.directoryInputs.forEach { dirInput ->
val dest = transformInvocation.outputProvider.getContentLocation(dirInput.name,
dirInput.contentTypes, dirInput.scopes,
Format.DIRECTORY)
FileUtils.forceMkdir(dest)
if (transformInvocation.isIncremental) {
val srcDirPath = dirInput.file.absolutePath
val destDirPath = dest.absolutePath
dirInput.changedFiles.forEach { (file, status) ->
val destFilePath = file.absolutePath.replace(srcDirPath, destDirPath)
val destFile = File(destFilePath)
when (status) {
Status.REMOVED -> {
FileUtils.deleteQuietly(destFile)
}
Status.CHANGED -> {
FileUtils.deleteQuietly(destFile)
asmWeaver.weaveClass(file, destFile)
}
Status.ADDED -> {
asmWeaver.weaveClass(file, destFile)
}
else -> {
}
}
}
} else {
dirInput.file.eachFileRecurse { file ->
asmWeaver.weaveClass(file, File(file.absolutePath.replace(dirInput.file.absolutePath, dest.absolutePath)))
}
}
}
input.jarInputs.forEach { jarInput ->
val dest = transformInvocation.outputProvider.getContentLocation(
jarInput.file.getUniqueJarName(),
jarInput.contentTypes,
jarInput.scopes,
Format.JAR)
if (transformInvocation.isIncremental) {
val status = jarInput.status
when (status) {
Status.REMOVED -> {
FileUtils.deleteQuietly(dest)
}
Status.CHANGED -> {
FileUtils.deleteQuietly(dest)
asmWeaver.weaveJar(jarInput.file, dest)
}
Status.ADDED -> {
asmWeaver.weaveJar(jarInput.file, dest)
}
else -> {
}
}
} else {
asmWeaver.weaveJar(jarInput.file, dest)
}
}
}
asmWeaver.start()
}
首先,初始化一个ASMWeaver对象asmWeaver,这个类主要用来asm插桩,稍后详细介绍。接下来根据是否支持增量,如果不支持,就删除outputProvider中的文件。然后就开始对输入transformInvocation进行遍历,里面的文件类型有目录和jar,需要进行不同的操作。下面先介绍对目录的操作,如果是目录,对于目录的每一项,先获取路径dest后。然后根据dest先创建目录,如果支持增量编译,对每个变动项根据状态进行不同的处理。如果是移除,则直接移除。如果是有修改,则将原有文件移除后,调用asmWeaver的weaveClass()。如果是新增文件,则直接调用asmWeaver的weaveClass()。如果不支持增量编译,则对dirInput的文件进行遍历,对每个文件调用asmWeaver的weaveClass()。上面介绍完了对目录的操作,下面开始介绍对jar的操作。对jar也是对是否支持增量编译进行分别处理,处理流程和对目录文件的大体类似,只是调用的方法变成了asmWeaver的weaveJar()。最后调用asmWeaver的start()。接下来介绍ASMWeaver的weaveClass()和weaveJar()。weaveClass的代码如下:
fun weaveClass(inputFile: File, outputFile: File) {
taskManager.addTask(object : ITask {
override fun call(): Any? {
FileUtils.touch(outputFile)
val inputStream = FileInputStream(inputFile)
val bytes = weaveSingleClassToByteArray(inputStream)
val fos = FileOutputStream(outputFile)
fos.write(bytes)
fos.close()
inputStream.close()
return null
}
})
}
ASMWeaver有个线程池taskManager,weaverCLass主要是向taskManager中添加task.添加的task是根据输入的文件调用weaveSingleClassToByteArray后,获得bytes后将bytes写入到weaveClass()的第二个参数的文件中。weaveSingleClassToByteArray会开始asm的插桩,代码如下:
private fun weaveSingleClassToByteArray(inputStream: InputStream): ByteArray {
val classReader = ClassReader(inputStream)
val classWriter = ExtendClassWriter(ClassWriter.COMPUTE_MAXS)
var classWriterWrapper: ClassVisitor = classWriter
if (PluginConfig.argusApmConfig().funcEnabled) {
classWriterWrapper = FuncClassAdapter(Opcodes.ASM4, classWriterWrapper)
}
if (PluginConfig.argusApmConfig().netEnabled) {
classWriterWrapper = NetClassAdapter(Opcodes.ASM4, classWriterWrapper)
}
if (PluginConfig.argusApmConfig().okhttpEnabled) {
classWriterWrapper = OkHttp3ClassAdapter(Opcodes.ASM4, classWriterWrapper)
}
if (PluginConfig.argusApmConfig().webviewEnabled) {
classWriterWrapper = WebClassAdapter(Opcodes.ASM4, classWriterWrapper)
}
classReader.accept(classWriterWrapper, ClassReader.EXPAND_FRAMES)
return classWriter.toByteArray()
}
代码的主要逻辑是将单个类进行插桩最后返回byteArray。代码中涉及到asm的几个核心类ClassReader,ClassVisitor ,ClassWriter,下面简单介绍下。
ClassReader类:分析以字节数组形式给出的已编译类,并针对在其 accept 方法参数中传送的 ClassVisitor 实例,调用相应的 visitXxx 方法。这个类可以看作一个事件产生器。
ClassVisitor 类:将它收到的所有方法调用都委托给另一个 ClassVisitor 类。这个类可以看作一个事件筛选器。
ClassWriter 类:是 ClassVisitor 抽象类的一个子类,它直接以二进制形式生成编译后的类。它会生成一个字节数组形式的输出,其中包含了已编译类,可以用 toByteArray 方法来提取。这个类可以看作一个事件使用器。
FuncClassAdapter,NetClassAdapter,OkHttp3ClassAdapter,WebClassAdapter这个类继承自BaseClassVisitor,而BaseClassVisitor继承自ClassVisitor 。这四个Adapter都是重写了visitMethod,就是说明只在method进行插桩 。这四个Adapter根据argusApmConfig的开关决定是否开启相关代码的插桩,之后调用classReader.accept(),最后返回classWrite的byteArray。下面分析下FuncClassAdapter这个类,代码如下:
class FuncClassAdapter(api: Int, cv: ClassVisitor?) : BaseClassVisitor(api, cv) {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor {
if (isInterface || !isNeedWeaveMethod(className, access)) {
return super.visitMethod(access, name, desc, signature, exceptions);
}
val mv = cv.visitMethod(access, name, desc, signature, exceptions)
if ((isRunMethod(name, desc) || isOnReceiveMethod(name, desc)) && mv != null) {
return FuncMethodAdapter(className.replace("/", "."), name, desc, api, access, desc, mv)
}
return mv
}
}
主要是重写了visitMethod。isInterface的判断在BaseClassVisitor,根据access判断。如果是接口,或者不需要插桩,就不处理直接返回,这样不会就不会修改class文件。是否需要插桩的判断代码如下:
fun isNeedWeaveMethod(className: String, access: Int): Boolean {
return isNeedWeave(className) && isNeedVisit(access)
}
fun isNeedWeave(className: String): Boolean {
if (PluginConfig.argusApmConfig().whitelist.size > 0) {
PluginConfig.argusApmConfig().whitelist.forEach {
if (className.startsWith(it.replace(".", "/"))) {
return true
}
}
return false
} else {
PluginConfig.argusApmConfig().includes.forEach {
if (className.startsWith(it.replace(".", "/"))) {
return true
}
}
PluginConfig.argusApmConfig().excludes.forEach {
if (className.startsWith(it.replace(".", "/"))) {
return false
}
}
return true
}
}
private fun isNeedVisit(access: Int): Boolean {
//不对抽象方法、native方法、桥接方法(编译器生成)、合成方法进行织入
if (access and Opcodes.ACC_ABSTRACT !== 0
|| access and Opcodes.ACC_NATIVE !== 0
|| access and Opcodes.ACC_BRIDGE !== 0
|| access and Opcodes.ACC_SYNTHETIC !== 0) {
return false
}
return true
}
如果需要插桩,并且不是特定的集中方法,就表示需要weave。是否需要weave可以由argusApmConfig来指定,就是我们在build.gradle的dsl中约定。
接下来继续分析FuncClassAdapter的visitMethod().如果是run或者onReceive方法,就调用FuncMethodAdapter进行插桩。分别在run和onReceive的函数执行前后进行插桩。代码如下:
private fun whenMethodEnter() {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
startTimeIndex = newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, startTimeIndex);
}
private fun whenOnReceiveMethodExit() {
mv.visitVarInsn(LLOAD, startTimeIndex)
mv.visitLdcInsn("method-execution")
mv.visitLdcInsn("void $className.onReceive(Context context, Intent intent)")
mv.visitVarInsn(ALOAD, 1)
mv.visitVarInsn(ALOAD, 2)
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 0)
mv.visitLdcInsn("${className.substring(className.lastIndexOf(".") + 1)}.java:$lineNumber")
mv.visitLdcInsn("execution(void $className.onReceive(Context context, Intent intent))")
mv.visitLdcInsn("onReceive")
mv.visitInsn(ACONST_NULL)
mv.visitMethodInsn(INVOKESTATIC, "com/argusapm/android/core/job/func/FuncTrace", "dispatch", "(JLjava/lang/String;Ljava/lang/String;Landroid/content/Context;Landroid/content/Intent;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", false)
}
private fun whenRunMethodExit() {
mv.visitVarInsn(LLOAD, startTimeIndex)
mv.visitLdcInsn("method-execution")
mv.visitLdcInsn("void $className.run()")
mv.visitInsn(ACONST_NULL)
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 0)
mv.visitLdcInsn("${className.substring(className.lastIndexOf(".") + 1)}.java:$lineNumber")
mv.visitLdcInsn("execution(void $className.run())")
mv.visitLdcInsn("run")
mv.visitInsn(ACONST_NULL)
mv.visitMethodInsn(INVOKESTATIC, "com/argusapm/android/core/job/func/FuncTrace", "dispatch", "(JLjava/lang/String;Ljava/lang/String;[Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", false)
}
这些方法的代码很难看懂,实际写的时候可以使用asm-bytecode-outline这个AS插件生成。如果想要看asm插桩之后的class文件,可以使用TraceClassVisitor这个工具。其他adapter的操作也类似,只是涉及到的类和插桩的地方不一样。限于功力,暂时先跳过。
下面开始介绍ASMWeaver的另外一个方法weaveJar(),代码如下:
fun weaveJar(inputJar: File, outputJar: File) {
taskManager.addTask(object : ITask {
override fun call(): Any? {
FileUtils.copyFile(inputJar, outputJar)
if (isWeaveThisJar(inputJar.name)) {
weaveJarTask(inputJar, outputJar)
}
return null
}
})
}
首先判断这个jar是否需要weave, 这个地方是由argusApmConfig来指定的。如果需要进调用weaveJarTask开始对jar进行weave.代码如下:
private fun weaveJarTask(input: File, output: File) {
var zipOutputStream: ZipOutputStream? = null
var zipFile: ZipFile? = null
try {
zipOutputStream = ZipOutputStream(BufferedOutputStream(Files.newOutputStream(output.toPath())))
zipFile = ZipFile(input)
val enumeration = zipFile.entries()
while (enumeration.hasMoreElements()) {
val zipEntry = enumeration.nextElement()
val zipEntryName = zipEntry.name
if (TypeUtil.isMatchCondition(zipEntryName) && TypeUtil.isNeedWeave(zipEntryName)) {
val data = weaveSingleClassToByteArray(BufferedInputStream(zipFile.getInputStream(zipEntry)))
val byteArrayInputStream = ByteArrayInputStream(data)
val newZipEntry = ZipEntry(zipEntryName)
ZipFileUtils.addZipEntry(zipOutputStream, newZipEntry, byteArrayInputStream)
} else {
val inputStream = zipFile.getInputStream(zipEntry)
val newZipEntry = ZipEntry(zipEntryName)
ZipFileUtils.addZipEntry(zipOutputStream, newZipEntry, inputStream)
}
}
} catch (e: Exception) {
} finally {
try {
if (zipOutputStream != null) {
zipOutputStream.finish()
zipOutputStream.flush()
zipOutputStream.close()
}
zipFile?.close()
} catch (e: Exception) {
log("close stream err!")
}
}
}
主要思路是遍历jar中的每个zipEntry,如果满足特定的条件,并且需要weave,就调用上面的weaveSingleClassToByteArray得到ByteArray.然后生成新的ZipEntry,之后调用ZipFileUtils的addZipEntry,将压缩后的文件输出到weaveJarTask()的out参数位置,如果不满足条件或不需要weave,就直接压缩。可以看到对jar的操作,实际对其中的每个类的weave操作。
最终在ArgusAPMTransform的transform()的最后调用asmWeaver的start,启动AsmWeaver中的所有task.
有什么不对的地方,请大家不吝指教。
学习的过程中,参考了网上的资料,感谢先行者的经验总结和无私分享。
网友评论