详解 Gradle Transform API

113 阅读4分钟

想象你正在组装一辆汽车(Android应用),Gradle 是自动化装配流水线。Transform API 就是在装配过程中拦截零件(.class 文件),让你有机会修改零件(字节码)再继续组装的特殊工位。

核心概念通俗版

  • 时机:在 Java 编译成 .class 文件后,打包成 .dex 文件前
  • 作用:拦截并修改编译过程中的中间产物(字节码)
  • 类比:就像在汽车组装线上,拦截所有发动机(某个类文件),给每个发动机加装涡轮增压(插入性能监控代码)

如何使用 Transform API(步骤详解)

1. 创建自定义 Transform 类

import com.android.build.api.transform.*

class MyCustomTransform extends Transform {
    // Transform 名称(必填)
    @Override
    String getName() {
        return "MyCustomTransform"
    }

    // 处理数据类型(必填)
    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        // 通常处理 CLASS 文件
        return TransformManager.CONTENT_CLASS
    }

    // 作用范围(必填)
    @Override
    Set<QualifiedContent.Scope> getScopes() {
        // 示例:当前项目 + 子模块 + 外部库
        return TransformManager.SCOPE_FULL_PROJECT
    }

    // 是否支持增量编译(推荐实现)
    @Override
    boolean isIncremental() {
        return true
    }

    // 核心处理逻辑(必填)
    @Override
    void transform(TransformInvocation invocation) {
        // 在这里处理字节码
    }
}

2. 注册 Transform(在自定义插件中)

class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        def android = project.extensions.getByType(AppExtension)
        android.registerTransform(new MyCustomTransform())
    }
}

AppExtension 是 Android Gradle 插件(AGP)中最核心的配置入口,它代表了 Android 应用模块(com.android.application)的所有构建配置选项。通俗来说,它就是你在 build.gradle 里 android { ... } 代码块背后的实际处理者

关键语法详解

1. 输入/输出处理

void transform(TransformInvocation invocation) {
    // 遍历所有输入
    invocation.inputs.each { TransformInput input ->
        // 处理目录输入(模块源码)
        input.directoryInputs.each { DirectoryInput dirInput ->
            File dest = invocation.outputProvider.getContentLocation(
                dirInput.name,
                dirInput.contentTypes,
                dirInput.scopes,
                Format.DIRECTORY
            )
            // 复制并处理文件
            processDir(dirInput.file, dest)
        }
        
        // 处理 JAR 输入(依赖库)
        input.jarInputs.each { JarInput jarInput ->
            File dest = invocation.outputProvider.getContentLocation(
                jarInput.name,
                jarInput.contentTypes,
                jarInput.scopes,
                Format.JAR
            )
            processJar(jarInput.file, dest)
        }
    }
}

2. 字节码操作(使用 ASM 示例)

void processClass(File input, File output) {
    FileInputStream fis = new FileInputStream(input)
    
    // 1. 解析.class文件
    ClassReader cr = new ClassReader(fis)
    
    // 2. 创建ClassWriter
    ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
    
    // 3. 创建自定义Visitor修改字节码
    ClassVisitor cv = new MyClassVisitor(Opcodes.ASM9, cw)
    
    // 4. 应用修改
    cr.accept(cv, 0)
    
    // 5. 输出修改后的字节码
    FileOutputStream fos = new FileOutputStream(output)
    fos.write(cw.toByteArray())
    fos.close()
}

3. 增量编译处理

void transform(TransformInvocation invocation) {
    if (!invocation.isIncremental()) {
        // 非增量模式:全量清理输出目录
        invocation.outputProvider.deleteAll()
    }

    invocation.inputs.each { input ->
        input.directoryInputs.each { dirInput ->
            File dir = dirInput.file
            File dest = invocation.outputProvider.getContentLocation(...)
            
            if (invocation.isIncremental()) {
                // 增量模式:只处理变更文件
                dirInput.changedFiles.each { File file, Status status ->
                    switch (status) {
                        case Status.ADDED:
                        case Status.CHANGED:
                            processFile(file, getOutputFile(dest, file))
                            break
                        case Status.REMOVED:
                            deleteOutputFile(dest, file)
                            break
                    }
                }
            } else {
                // 全量处理
                processDir(dir, dest)
            }
        }
    }
}

源码调用流程(Android Gradle 插件内部)

  1. 任务创建

    • TaskManager.createPostCompilationTasks()
    • 在 transformClassesWith... 任务链中插入自定义 Transform
  2. 执行顺序

deepseek_mermaid_20250621_8de4b1.png

  1. 核心处理

    • TransformManager.addTransform() 注册 Transform
    • TransformTask 负责实际执行
    • TransformStream 处理输入输出流

常见应用场景示例

示例 1:方法耗时统计

// 自定义 ClassVisitor
public class TimeCostVisitor extends ClassVisitor {
    @Override
    public MethodVisitor visitMethod(...) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        return new MethodVisitor(Opcodes.ASM9, mv) {
            @Override
            public void visitCode() {
                // 方法开始插入代码
                mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
                mv.visitVarInsn(LSTORE, 1);
                super.visitCode();
            }
            
            @Override
            public void visitInsn(int opcode) {
                if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
                    // 方法结束插入代码
                    mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
                    mv.visitVarInsn(LLOAD, 1);
                    mv.visitInsn(LSUB);
                    // 调用日志方法
                    mv.visitLdcInsn(name);
                    mv.visitMethodInsn(INVOKESTATIC, "com/example/TimeRecorder", "record", "(JLjava/lang/String;)V", false);
                }
                super.visitInsn(opcode);
            }
        };
    }
}

示例 2:全局异常捕获

public class ExceptionHandlerVisitor extends ClassVisitor {
    @Override
    public MethodVisitor visitMethod(...) {
        MethodVisitor mv = super.visitMethod(...);
        if ("onCreate".equals(name)) {
            return new TryCatchWrapper(mv, className, name);
        }
        return mv;
    }
}

class TryCatchWrapper extends MethodVisitor {
    // 在方法开头添加 try 块
    public void visitCode() {
        mv.visitTryCatchBlock(...);
        super.visitCode();
    }
    
    // 在异常处理块插入上报代码
    public void visitMaxs(int maxStack, int maxLocals) {
        Label end = new Label();
        mv.visitLabel(end);
        // 插入异常上报代码
        mv.visitMethodInsn(INVOKESTATIC, "com/error/CrashReporter", "report", "(Ljava/lang/Throwable;)V", false);
        mv.visitInsn(ATHROW);
        super.visitMaxs(maxStack, maxLocals);
    }
}

示例 3:路由框架自动注册

// 收集所有带@Route注解的类
public void processClass(File file) {
    ClassReader cr = new ClassReader(file.bytes)
    cr.accept(new ClassVisitor(Opcodes.ASM9) {
        String className;
        
        @Override
        public void visit(String version, int access, String name, String signature, String superName, String[] interfaces) {
            this.className = name;
            super.visit(...)
        }
        
        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            if ("Lcom/example/Route;".equals(desc)) {
                // 发现路由注解,记录类名
                RouteCollector.add(className)
            }
            return super.visitAnnotation(desc, visible)
        }
    }, 0)
}

// 生成注册代码
void generateRegistry() {
    ClassWriter cw = new ClassWriter(0)
    cw.visit(Opcodes.V1_8, ACC_PUBLIC, "com/example/RouteRegistry", null, "java/lang/Object", null)
    
    MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "init", "()V", null, null)
    for (String className : collectedClasses) {
        mv.visitLdcInsn(className)
        mv.visitMethodInsn(INVOKESTATIC, "com/example/Router", "register", "(Ljava/lang/String;)V", false)
    }
    mv.visitInsn(RETURN)
    mv.visitMaxs(1, 0)
    
    // 写入生成的文件
}

最佳实践与注意事项

  1. 性能优化

    • 必须实现增量编译(isIncremental() 返回 true)
    • 使用高效的字节码操作库(ASM 优于 Javassist)
    • 避免修改非目标文件(通过 scopes 精确控制)
  2. 兼容性处理

    // 避免处理 Android 系统类
    if (className.startsWith("android/") || 
        className.startsWith("androidx/")) {
        return
    }
    
  3. 调试技巧

    # 查看 Transform 执行顺序
    ./gradlew assembleDebug --dry-run
    
    # 输出修改后的字节码
    -dontobfuscate
    -dontoptimize
    
  4. 常见问题排查

    • 类冲突:检查 transform 顺序
    • 增量编译失效:正确处理文件状态
    • 堆栈溢出:优化 ASM 访问逻辑

总结对比表

特性Transform API注解处理器 (APT)
处理阶段字节码级别 (.class)源代码级别 (.java)
修改能力可修改现有代码只能生成新代码
性能影响较大(需操作字节码)较小
典型应用性能监控、热修复代码生成、DI 框架
技术门槛高(需字节码知识)
输入类型.class 文件.java 文件

Transform API 是 Android 构建系统中强大的底层工具,它让你能够在编译过程中直接操作字节码,实现诸如性能监控、代码注入、热修复等高级功能