1. 基本概念
1.1 java字节码
Java字节码是Java虚拟机执行的一种虚拟指令格式。
可通过javac 编译java文件得到字节码文件。
javap 分析字节码文件内容。
1.2 插桩
面向切面编程的一种编程实现。
在需要统一处理的切面上,增加额外代码功能。
具体而言就是在编译期(class --> dex 阶段)修改class文件的结构,达到统一处理目的。
2. demo目标
针对被注解修饰的全部方法,执行插桩,方法进入及离开时,记录系统时间,以实现统计方法执行耗时的目的。
3. 实现步骤
3.1 自定义gradle 组件
目前网上可以搜出大把的通过ASM 插件的方式,实现自定义的gradle 插桩组件,也可直接参考别的文章。
根据作用范围不同,可将gradle 插件分为三类:
- 直接在module build.gradle 中编写:仅作用在自身的build.gradle 文件中
- buildSrc目录:插件部分源码放在 buildSrc/src/main/groovy/ 中,只对本项目中可见。
- 独立的java module(new module 时选择 java or kotlin Libray): 可以发布到jcenter或者maven仓库,别的项目可以直接引入。
demo 中并未采用 常见的ASM “org.ow2.asm:asm:7.2” ,“org.ow2.asm:asm-commons:7.2” 之类的插件,因为“java-gradle-plugin”最近的版本 已经集成了相关功能,无需再借助单独的ASM 插件了。
3.2 定义注解类
实际上就是限制插桩的作用范围。
3.3 关于ASM 核心原理
访问者模式
ClassReader:它将字节数组或者 class 文件读入到内存,以树的数据结构表示,树中的一个节点代表着 class 文件中的某个区域。
ClassVisitor(抽象类):调用 ClassReader#accept() 方法,入参为一个 ClassVisitor 对象。ClassReader 遍历树结构的不同节点时会调用 ClassVisitor 对象中不同的 visit() 方法,从而实现对字节码的修改。
如果有不同操作功能的ClassVisitor,则使用责任链模式,逐级传递。
ClassWriter:ClassWriter 是 ClassVisitor 的实现类,它是生成字节码的工具类,一般是责任链中的最后一个节点,其之前的每一个 ClassVisitor 责任为修改原始字节码,而 ClassWriter 的操作则是把每一个节点修改后的字节码输出为字节数组。
ASM 大致的工作流程是:
ClassReader: 读取字节码到内存中,生成用于表示该字节码的树结构,对应于访问者模式中的元素;
组装 ClassVisitor 责任链: 完成对不同的字节码修改工作,对应于访问者模式中的访问者 Visitor
ClassReader#accept() :传入 ClassVisitor 对象,此 ClassVisitor 是责任链的头结点,逐级传递给责任链中每一个 ClassVisitor,实现对加载进内存的字节码的树结构上的每个节点的访问和修改。
ClassWriter:通常在在责任链的末端,输出修改后的字节码
4 核心代码
public class TimeMethodVisitor extends MethodVisitor {
...
@Override
AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (desc.contains("InjectTimestamp")) {
isInject = true
}
return super.visitAnnotation(desc, visible)
}
@Override
void visitCode() {
if (isInject) {
mv.visitLdcInsn(className + " -> TAG");
mv.visitLdcInsn("开始时间:" + System.currentTimeMillis());
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "e", "(Ljava/lang/String;Ljava/lang/String;)I", false);
mv.visitInsn(Opcodes.POP);
}
super.visitCode()
}
@Override
void visitInsn(int opcode) {
if (isInject && opcode == Opcodes.RETURN) {
mv.visitLdcInsn(className + " -> TAG");
mv.visitLdcInsn("结束时间:" + System.currentTimeMillis());
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "e", "(Ljava/lang/String;Ljava/lang/String;)I", false);
mv.visitInsn(Opcodes.POP);
}
super.visitInsn(opcode)
}
}
class TimePluginTransform extends Transform {
@Override
void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
transformInvocation.inputs.each { TransformInput input ->
input.directoryInputs.each { DirectoryInput directoryInput ->
if (directoryInput.file.isDirectory()) {
directoryInput.file.eachFileRecurse { File file ->
tranformFile(file)
}
} else {
tranformFile(file)
}
// Transform 拷贝文件到 transforms 目录
File dest = transformInvocation.outputProvider.getContentLocation(
directoryInput.getName(),
directoryInput.getContentTypes(),
directoryInput.getScopes(),
Format.DIRECTORY);
// 将修改过的字节码copy到dest,实现编译期间干预字节码
FileUtils.copyDirectory(directoryInput.getFile(), dest);
}
input.jarInputs.each { JarInput jarInput ->
def jarName = jarInput.name
def dest = transformInvocation.outputProvider.getContentLocation(jarName,
jarInput.contentTypes, jarInput.scopes, Format.JAR)
FileUtils.copyFile(jarInput.getFile(), dest)
}
}
}
5 关于插件扩展
例如系统提供的 apply plugin: 'com.android.application'

定义方法:
project.getExtensions().create(“扩展名称", JavaBean.class);
demo暂未涉及
网友评论