当前位置: 首页 > article >正文

Android ASM 修改 .class 文件

在 Android 开发中,ASM 是一个通用的字节码操控和分析框架,它主要用于读取、修改和生成 Java 字节码。ASM 全称为 “Abstract Syntax Manipulation”,可以直接操作 .class 文件,使开发者能够在字节码层面进行性能优化、插桩代码、性能监测和其他操作,而不需要关注 Java 源代码。这在需要动态修改字节码的库(如AOP框架)中非常常用。


    testImplementation("org.ow2.asm:asm:9.6")
    testImplementation("org.ow2.asm:asm-commons:9.6")

由于是测试模块使用,所以用 testImplementation


创建一个测试类 TestMain:

public class TestMain {
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(1000);
    }
}

然后使用 javac 把这个这个文件编译成 .class 文件

javac D:\demo\my_demo\app\src\test\java\com\example\my_demo\TestMain.java

这个路径是该文件的全路径(自行替换)

编译好之后该文件路径下就会多出一个 TestMain.class,可以双击点开查看:


假设要对这个 class 文件中的方法加一个耗时打印,则需要在 class 文件中的方法的开头和结尾都加一些逻辑

在执行的 main 方法中:

fun main() {
    val fis = FileInputStream("D:\\demo\\my_demo\\app\\src\\test\\java\\com\\example\\my_demo\\TestMain.class")

    val classReader = ClassReader(fis)
    val classWrite = ClassWriter(ClassWriter.COMPUTE_FRAMES)

    val classVisitor = object : ClassVisitor(Opcodes.ASM9, classWrite) {
        // 解析到方法时,回调此函数
        override fun visitMethod(access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
            val rawVisitMethod = super.visitMethod(access, name, descriptor, signature, exceptions)
            return MyMethodVisitor(Opcodes.ASM9, rawVisitMethod, access, name, descriptor)
        }
    }
    classReader.accept(classVisitor, 0) // 开始访问 Class 文件

    // 将新 Class 文件输出
    val fos = FileOutputStream("D:\\demo\\my_demo\\app\\src\\test\\java\\com\\example\\my_demo\\TestMain2.class")
    fos.write(classWrite.toByteArray())
    fos.close()
}

首先拿到 class 文件,通过 ClassReader 和 ClassVisitor 配合使用,解析 class 文件中的内容(方法、字段等信息都会通过接口回调回来)。我们就可以根据回调回来的信息进行手动修改,修改完成后通过 ClassWriter 输出成一个新的 class 文件


自定义的方法修改类 MyMethodVisitor:

class MyMethodVisitor(api: Int, methodVisitor: MethodVisitor?, access: Int, name: String?, descriptor: String?) : AdviceAdapter(api, methodVisitor, access, name, descriptor) {
    var start = 0

    // 方法开始时调用
    override fun onMethodEnter() {
        super.onMethodEnter()

        // 插入:long start = System.currentTimeMillis();
        invokeStatic(Type.getType("Ljava/lang/System;"), Method("currentTimeMillis", "()J"))
        start = newLocal(Type.LONG_TYPE)
        storeLocal(start)
    }

    // 方法结束时调用
    override fun onMethodExit(opcode: Int) {
        super.onMethodExit(opcode)

        // 插入:long end = System.currentTimeMillis();
        invokeStatic(Type.getType("Ljava/lang/System;"), Method("currentTimeMillis", "()J"))
        val end = newLocal(Type.LONG_TYPE)
        storeLocal(end)

        // 插入:System.out.println(end - start);
        getStatic(Type.getType("Ljava/lang/System;"), "out", Type.getType("Ljava/io/Printstream;"))
        loadLocal(end)
        loadLocal(start)
        math(GeneratorAdapter.SUB, Type.LONG_TYPE)
        invokeVirtual(Type.getType("Ljava/io/Printstream;"), Method("println", "(Ljava/lang/String;)V"))
    }
}

通过这样子配置后,运行一下 main 方法,就会在路径下多出一个 TestMain2.class 文件,双击点开查看:

这样子通过 ASM 来对 class 文件进行修改和输出成一个新的 class 文件。例子中是对 class 文件中所有的方法都进行插桩了,若只需要特定方法的话,在 ClassVisitor 的回调方法中进行方法名的判断即可


ASM 还可以在构建 APK 过程中结合 Gradle 插件进行使用,从而实现自动化代码修改或增强。例如,在构建 .class 文件时自动插入一些想要的逻辑。


http://www.kler.cn/a/385509.html

相关文章:

  • 【信号处理】基于联合图像表示的深度学习卷积神经网络
  • YOLOv11融合ICCV[2023]动态蛇形卷积Dynamic模块及相关改进思路|YOLO改进最简教程
  • 将 Docker 镜像保存到本地文件系统中
  • 【大数据学习 | HBASE】hbase的读数据流程与hbase读取数据
  • kafka夺命连环三十问(16-22)
  • C# 软件测试
  • Qt的跨平台介绍
  • 【Linux】简易版shell
  • 生产环境中添加多项式特征实现:将逻辑回归应用于非线性关系
  • Linux操作系统:学习进程_了解并掌握进程的状态
  • 手机内卷下一站,AI Agent
  • java抽象类
  • SQL 注入(文件读取)
  • UE5.4 PCG 复制关卡实例
  • 线程级耗时统计工具类TimeWatcher
  • 深度学习-图像评分实验(TensorFlow框架运用、读取处理图片、模型建构)
  • 【数据结构】快慢指针探秘:理解链表与数组中的环结构
  • Leecode热题100-78.子集
  • 【AIGC探索】AI实现PPT生产全流程
  • 《Python使用sqlite3数据库》
  • Pytorch基本语法
  • 微软域名邮箱:如何设置管理烽火域名邮箱?
  • .NET6中WPF项目添加System.Windows.Forms引用
  • oracle数据坏块处理(三)-数据抽取插入到新表中
  • webWorker基本用法
  • 容器化技术入门:Docker详解