Gradle Transform和ASM

Gradle Transform和ASM

本文链接:https://blog.csdn.net/feather_wch/article/details/131753544

文章目录

关键词:
transform
asm
org.ow2.asm:asm

项目路径:https://github.com/FeatherHunter/StudyNotes/blob/master/assets/DemoProjects/ASMInject.zip

Trasnform

1、Gradle Transform是什么。😊

  1. Gradle Transform是Android官方提供给开发者在项目构建阶段(.class ->.dex转换期间)用来修改.class文件的一套标准API
  2. 可以在不修改源代码的情况下,对.class文件进行自定义的操作,例如插入字节码,修改注解,替换资源等。
  3. Gradle Transform可以用于实现一些高级的功能,例如热修复,代码注入,AOP等。

2、Gradle构建的阶段

  • 初始化阶段:Gradle会加载项目的配置信息,检查依赖关系,创建任务图等。
  • 编译阶段:Gradle会调用Java编译器将源代码转换成.class文件,并调用AAPT工具将资源文件转换成二进制格式。
  • Transform阶段:Gradle会调用Transform API对输入的.class文件进行自定义的操作,例如插入字节码,修改注解,替换资源等。Transform可以用于实现一些高级的功能,例如热修复,代码注入,AOP等。
  • DEX阶段:radle会调用DX工具将.class文件转换成DEX文件,以适应Android平台的运行环境。
  • 打包阶段:Gradle会调用APK打包器将DEX文件和已编译资源合并成单个APK文件,并对其进行签名和对齐等操作。
  • 安装和运行阶段:Gradle会将APK文件安装到目标设备或模拟器上,并启动应用程序。

3、Transform阶段的资源来自于

  1. class
  2. jar
  3. resources:特指和java平级的resources文件夹中资源。不是Android的res文件。

4、> Task :app:preBuild UP-TO-DATE => 代表最新的没有变化

Transform实战

ASMPlugin.groovy:Plugin插件,apply中注册Transform

java 复制代码
class ASMPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {

// 注册Transform ASMTransform
        project.extensions.getByType(AppExtension.class).registerTransform(new ASMTransform())
    }
}

ASMTransform.groovy:

class ASMTransform extends Transform {
// 1、任务的名称
    @Override
    String getName() {
        return "asm";
    }
// 2、可以是Class可以是resources这里只需要Class
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }
// 3、只对引入插件的模块生效
    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.PROJECT_ONLY
    }

    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        TransformOutputProvider outputProvider = transformInvocation.outputProvider
        //清理文件
        outputProvider.deleteAll()

        def inputs = transformInvocation.inputs
        inputs.each {
// 4、因为是transformManager.PROJECT_ONLY 只管当前的src目录,不管jar目录,不需要 it.jarInputs
            def directoryInputs = it.directoryInputs
            directoryInputs.each {
                String dirName = it.name
                File src = it.getFile();
                println("源目录:" + src)
// 5、transform是一个一个执行的,上一个transoform的输出是下一个的输入。
// 源目录:F:\5.Android\Projects\ASMInject\ASMInject\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes
// 目标目录:F:\5.Android\Projects\ASMInject\ASMInject\app\build\intermediates\transforms\asm\debug\0
                String md5Name = DigestUtils.md5Hex(src.absolutePath)
                File dest = outputProvider.getContentLocation(dirName + md5Name, // 必须保证是唯一的
                        it.contentTypes, // 类型
                        it.scopes, // 作用域
                        Format.DIRECTORY);
//根据这些参数,outputProvider.getContentLocation 会返回一个 File 对象,表示这个内容的位置。如果格式是 Format.DIRECTORY ,那么结果是一个目录的文件位置;如果格式是 Format.JAR ,那么结果是一个表示要创建的 jar 包的文件。
                println("目标目录:" + dest)
                println("dirName:" + dirName) // dirName:e47eef71c08b0d42c571e631eb55645c006d92a2
                println("md5Name:" + md5Name) // md5Name:5f3e38731d3c990d7ad79d6404c3b796
                //插桩
                processInject(src, dest);
            }
        }
    }

    void processInject(File src, File dest) {
        String dir = src.absolutePath
        FluentIterable<File> allFiles = FileUtils.getAllFiles(src)
        for (File file : allFiles) {
            println("需要插桩的文件:" + file.name)
            FileInputStream fis = new FileInputStream(file)
            //插桩
            ClassReader cr = new ClassReader(fis)
            // 写出器
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES)
            //分析,处理结果写入cw
            cr.accept(new ClassInjectTimeVisitor(cw, file.name), ClassReader.EXPAND_FRAMES)

            byte[] newClassBytes = cw.toByteArray()
            //class文件绝对地址
            String absolutePath = file.absolutePath
            //class文件绝对地址去掉目录,得到全类名
            String fullClassPath = absolutePath.replace(dir, "")
            File outFile = new File(dest, fullClassPath)
            FileUtils.mkdirs(outFile.parentFile)
            FileOutputStream fos = new FileOutputStream(outFile)
            fos.write(newClassBytes)
            fos.close()
        }

    }
}

ClassInjectTimeVisitor.groovy

java 复制代码
    class ClassInjectTimeVisitor extends ClassVisitor {

        String className

        ClassInjectTimeVisitor(ClassVisitor cv, String fileName) {
            super(Opcodes.ASM5, cv);
            className = fileName.substring(0, fileName.lastIndexOf("."))
        }


        @Override
        MethodVisitor visitMethod(int access, String name, String desc, String signature,
                                  String[] exceptions) {

            MethodVisitor mv = super.visitMethod(access, name, desc, signature,
                    exceptions)
    // 修改方法
            return new MethodAdapterVisitor(mv, access, name, desc, className)
        }

    }

MethodAdapterVisitor.groovy

java 复制代码
    class MethodAdapterVisitor extends AdviceAdapter {

        private String className;
        private String methodName;
        private boolean inject;
        private int index;
        private int start, end;

        protected MethodAdapterVisitor(MethodVisitor mv, int access, String name, String desc,
                                       String className) {
            super(Opcodes.ASM5, mv, access, name, desc);
            methodName = name;
            this.className = className;
        }

        @Override
        AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            //记录方法是不是被 注解
            if ("Lcom/enjoy/asminject/InjectTime;".equals(desc)) {
                inject = true;
            }
            return super.visitAnnotation(desc, visible);
        }


        @Override
        protected void onMethodEnter() {
            if (inject) {
    //                0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
    //                3: lstore_1
                //储备本地变量备用
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                index = newLocal(Type.LONG_TYPE);
                start = index;
                mv.visitVarInsn(LSTORE, start);
            }

        }


        @Override
        protected void onMethodExit(int opcode) {
            if (inject) {
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                index = newLocal(Type.LONG_TYPE);
                end = index;
                mv.visitVarInsn(LSTORE, end);

                // getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
                //获得静态成员 out
                mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");

                // new #4 // class java/lang/StringBuilder
                // 引入类型 分配内存 并dup压入栈顶让下面的INVOKESPECIAL 知道执行谁的构造方法
                mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
                mv.visitInsn(DUP);

                //invokevirtual #7   // Method java/lang/StringBuilder.append:
                // (Ljava/lang/String;)Ljava/lang/StringBuilder;
                // 执行构造方法
                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>",
                        "()V", false);

                // ldc #6 // String execute:
                // 把常量压入栈顶 后面使用
                mv.visitLdcInsn("==========>" + className + " execute " + methodName + ": ");

                //invokevirtual #7 // Method java/lang/StringBuilder.append: (Ljava/lang/String;)
                // Ljava/lang/StringBuilder;
                // 执行append方法,使用栈顶的值作为参数
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                        "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

                // lload_3 获得存储的本地变量
                // lload_1
                // lsub   减法指令
                mv.visitVarInsn(LLOAD, end);
                mv.visitVarInsn(LLOAD, start);
                mv.visitInsn(LSUB);

                // invokevirtual #8 // Method java/lang/StringBuilder.append:(J)
                // Ljava/lang/StringBuilder;
                // 把减法结果append
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                        "(J)Ljava/lang/StringBuilder;", false);

                //append "ms."
                mv.visitLdcInsn("ms.");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                        "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

                //tostring
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString",
                        "()Ljava/lang/String;", false);

                mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
                        "(Ljava/lang/String;)V", false);
            }
        }
    }

ASM

ASM:修改字节码的技术

app的build.gradle

    testImplementation 'org.ow2.asm:asm:7.1'
    testImplementation 'org.ow2.asm:asm-commons:7.1'

会在需要的目标方法中添加打印时间的日志:

==========>MainActivity execute a: 2001ms.
==========>MainActivity execute onCreate: 2067ms.
相关推荐
bjzhang754 天前
Maven vs Gradle,如何选择?
gradle·maven
ItJavawfc5 天前
Grandle 报错_项目无法编译问题解决
android·gradle
孑么6 天前
GDPU Andriod移动应用 Fragment碎片与简易版的新闻应用
android·xml·java·kotlin·gradle·android-studio
大耳猫9 天前
Android gradle和maven国内镜像地址
android·gradle·maven
kfepiza9 天前
Win11GBK, idea2024.2.4, 使用Gradle8.8本地安装构建,不使用包装器, 解决utf-8乱码问题, 笔记241028
笔记·gradle·intellij-idea·idea·intellij idea
ccmjga10 天前
建造者设计模式
java·spring boot·设计模式·gradle·spring security·1024程序员节
ccmjga11 天前
为什么选择 Spring data hadoop
java·spring boot·docker·设计模式·gradle·spring security
wodongx12313 天前
Task :prepareKotlinBuildScriptModel UP-TO-DATE,编译卡在这里不动或报错
android·gradle
ccmjga14 天前
适配器设计模式
java·spring boot·后端·设计模式·gradle·spring security·1024程序员节
大耳猫17 天前
Android 构建时出现Namespace not specified.
android·gradle·android studio