从组件化到字节码操控:Gradle+ASM全流程实战(AGP 8

2025-03-22  本文已影响0人  野火友烧不尽

(AGP 8.x以下版)Gradle+Transform+Asm自动化注入代码

一、原始方案回顾

1. 核心场景

通过Gradle插件+Transform API+ASM,在编译期自动为标记了@ComponentRegister的类生成注册代码到ComponentManager,实现组件化无反射初始化。

2. 原始代码架构

--> A[App工程]
--> B[自定义Gradle插件]
--> C[Transform扫描class文件]
--> D[ASM修改ComponentManager]
--> E[生成注册代码]

二、升级到AGP 8.x的必要性

特性 Transform API(旧) AsmClassVisitorFactory(新)
AGP兼容性 最高支持到6.7 4.2+(推荐8.1.2)
字节码操作 基于文件IO 内存直接操作,无IO损耗
增量编译 需手动实现 内置支持,速度提升40%

三、ASM核心操作详解

1. 修改类继承关系

目标:让TargetClass继承BaseClass

public class ChangeSuperClassVisitor extends ClassVisitor {
    public ChangeSuperClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    @Override
    public void visit(int version, int access, String name, 
                      String signature, String superName, 
                      String[] interfaces) {
        // 修改父类为"com/example/BaseClass"
        super.visit(version, access, name, signature, "com/example/BaseClass", interfaces);
    }
}

2. 方法织入代码

目标:在onCreate前后插入日志

public class MethodWeavingVisitor extends ClassVisitor {
    public MethodWeavingVisitor(ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, 
                                     String desc, String signature, 
                                     String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        if ("onCreate".equals(name)) {
            return new AdviceAdapter(mv, access, name, desc) {
                @Override
                protected void onMethodEnter() { // 方法开始
                    mv.visitLdcInsn("Enter: " + name);
                    mv.visitMethodInsn(INVOKESTATIC, "Logger", "log", "(Ljava/lang/String;)V", false);
                }

                @Override
                protected void onMethodExit(int opcode) { // 方法结束
                    mv.visitLdcInsn("Exit: " + name);
                    mv.visitMethodInsn(INVOKESTATIC, "Logger", "log", "(Ljava/lang/String;)V", false);
                }
            };
        }
        return mv;
    }
}

3. 处理泛型与注解

目标:打印类上的@Deprecated注解和泛型信息

public class GenericVisitor extends ClassVisitor {
    private String className;

    public GenericVisitor(ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    @Override
    public void visit(int version, int access, String name, 
                      String signature, String superName, 
                      String[] interfaces) {
        className = name;
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (desc.equals(Type.getDescriptor(Deprecated.class))) {
            System.out.println(className + " is deprecated");
        }
        return super.visitAnnotation(desc, visible);
    }

    @Override
    public void visitTypeParameter(TypeParameter[] typeParameters) {
        for (TypeParameter tp : typeParameters) {
            System.out.println("Generic: " + tp.getName());
        }
    }
}

四、AGP 8.x完整实现:组件自动注册

1. 项目结构

project-root/
├─ app/                          # 应用模块
│  ├─ src/main/java/
│  │  ├─ com/example/
│  │  │  ├─ ComponentManager.java  # 自动生成注册代码
│  │  │  └─ components/
│  │  │     ├─ ComponentA.java     # 标记@ComponentRegister
│  └─ build.gradle
└─ buildSrc/                     # Gradle插件模块
   └─ src/main/java/
      └─ com/example/
         ├─ ComponentPlugin.java    # 插件入口
         └─ visitor/
            ├─ ComponentClassVisitorFactory.java # Visitor工厂
            └─ ComponentClassVisitor.java       # 核心Visitor

2. 核心代码实现

Step 1:定义注解与接口

// IComponent.java
public interface IComponent { void init(); }

// ComponentRegister.java
@Retention(RetentionPolicy.CLASS)
public @interface ComponentRegister {
    String priority() default "NORMAL";
}

Step 2:Gradle插件入口

// ComponentPlugin.kt
class ComponentPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val android = project.extensions.getByType(AppExtension::class.java)
        android.registerTransform(
            AsmClassVisitorFactory.create(
                ComponentClassVisitorFactory::class.java,
                InstrumentationScope.PROJECT,
                true // 开启增量编译
            )
        )
    }
}

Step 3:Visitor工厂与核心逻辑

// ComponentClassVisitorFactory.java
public class ComponentClassVisitorFactory 
    implements AsmClassVisitorFactory<ComponentClassVisitorFactory.Parameters> {

    public static class Parameters { }

    @Override
    public ClassVisitor createClassVisitor(
        ClassContext classContext, 
        ClassVisitor nextClassVisitor
    ) {
        return new ComponentClassVisitor(nextClassVisitor);
    }

    @Override
    public boolean isInstrumentable(ClassData classData) {
        return classData.getAnnotations().contains(
            "Lcom/example/ComponentRegister;"
        );
    }
}

// ComponentClassVisitor.java
public class ComponentClassVisitor extends ClassVisitor {
    private final List<String> componentClasses = new ArrayList<>();
    private String currentClassName;

    public ComponentClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    @Override
    public void visit(int version, int access, String name, 
                      String signature, String superName, 
                      String[] interfaces) {
        currentClassName = name;
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (desc.equals(Type.getDescriptor(ComponentRegister.class))) {
            componentClasses.add(currentClassName);
        }
        return super.visitAnnotation(desc, visible);
    }

    @Override
    public void visitEnd() {
        if ("com/example/ComponentManager".equals(currentClassName)) {
            generateRegistrationCode();
        }
        super.visitEnd();
    }

    private void generateRegistrationCode() {
        MethodVisitor mv = cv.visitMethod(
            Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC,
            "registerComponents",
            "()V",
            null,
            null
        );
        mv.visitCode();

        // 按优先级排序组件(示例)
        componentClasses.stream()
            .sorted(Comparator.comparingInt(c -> {
                // 从注解中获取优先级
                return ComponentRegisterPriority.getPriority(c);
            }))
            .forEach(this::generateComponentCode);

        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    private void generateComponentCode(String className) {
        mv.visitTypeInsn(Opcodes.NEW, className);
        mv.visitInsn(Opcodes.DUP);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, className, "<init>", "()V", false);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, 
            "com/example/ComponentManager", 
            "addComponent", 
            "(Lcom/example/IComponent;)V", 
            false
        );
    }
}

五、ASM高级技巧与工具

1. 使用AdviceAdapter简化开发

优势:自动处理操作数栈,避免手动计算偏移量

public class AdviceAdapterDemo extends AdviceAdapter {
    public AdviceAdapterDemo(MethodVisitor mv, int access, 
                           String name, String desc) {
        super(Opcodes.ASM9, mv, access, name, desc);
    }

    @Override
    protected void onMethodEnter() {
        // 插入局部变量
        int localVar = newLocal(Type.getType(String.class));
        visitLdcInsn("Method entered: " + name);
        visitVarInsn(ASTORE, localVar);
    }
}

2. ASM Bytecode Viewer使用

  1. 下载GitHub Release
  2. 反编译class
javap -c -p ModifiedClass.class
  1. 对比字节码:通过Compare功能查看修改前后差异

六、常见问题与解决方案

1. 类找不到异常

现象ClassNotFoundException: ComponentManager
原因:插件未正确生成类或混淆导致
解决方案

2. 栈帧计算错误

现象java.lang.VerifyError
原因:手动计算栈帧大小错误
解决方案:使用ClassWriter.COMPUTE_FRAMES模式

ClassWriter writer = new ClassWriter(ClassReader, ClassWriter.COMPUTE_FRAMES);

3. 泛型信息丢失

原因:ASM默认不保留泛型签名
解决方案:在ClassReader.accept()中启用EXPAND_FRAMES

reader.accept(cv, ClassReader.EXPAND_FRAMES);

七、性能优化

1. 多线程处理

// 使用ForkJoinPool并行扫描类
ForkJoinPool.commonPool().submit(() -> {
    inputs.forEach(this::processInput);
}).get();

2. 缓存未修改文件

// 插件中实现增量缓存
transformInvocation.outputProvider.deleteAll()
inputs.forEach { input ->
    input.incrementalFileSet?.let {
        it.getAddedFiles().forEach(::processFile)
        it.getChangedFiles().forEach(::processFile)
    }
}

八、总结

本文通过AGP 8.x的AsmClassVisitorFactoryASM 9,实现了从组件化注册到字节码精细操控的全流程。核心优势包括:

上一篇 下一篇

猜你喜欢

热点阅读