自学内容网 自学内容网

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 文件时自动插入一些想要的逻辑。


原文地址:https://blog.csdn.net/weixin_47592544/article/details/143424957

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!