Android字节码编辑器,Android字节码插桩demo

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'

4b8f9c833035

image.png

定义方法:

project.getExtensions().create(“扩展名称", JavaBean.class);

demo暂未涉及

6 源码地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值