You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android编译前修改代码的最优Gradle方案咨询

Android编译前修改代码且不改动本地原代码的最优方案

需求背景

要开发一款类似ProGuard的自定义Gradle插件,核心目标是仅修改编译阶段使用的代码,完全保留本地原代码,已掌握反射、自定义Gradle插件、自定义注解相关技术。

现有初步方案分析

当前采用「备份-修改-恢复」的流程:

  1. 执行backupTask备份src/main/java目录
  2. 修改原代码
  3. 构建完成后恢复备份的原代码

对应的示例代码:

task backupCode(type:Copy){
    def src = "src/main/java"
    def dst = "src/main/backup"
    from src
    into dst
    filter {String line ->
        line.replace('Hello',"Bello")
    }
    dependsOn 'deleteModified'
}
task restoreCode(type:Copy){
    def src = "src/main/backup"
    def dst = "src/main/java"
    from src
    into dst
}
task deleteModified(type:Delete){
    delete "src/main/modified"
}
preBuild{
    dependsOn 'generateModifiedCode'
    doLast {
        //restore
        restoreCode()
    }
}

但这个方案存在明显缺陷:

  • 直接操作本地源码目录,若构建中断(手动取消、编译报错)易导致原代码未恢复,引发代码丢失风险
  • 备份/恢复过程产生冗余文件,增加项目体积
  • 基于文本替换的修改逻辑,面对复杂语法(嵌套函数、多行代码)时极易出错

最优实现方案

方案1:Gradle SourceSet + AST解析 隔离源码

核心思路

复制原源码到临时编译目录,在临时目录中通过AST解析修改代码,再将临时目录加入Gradle的SourceSet作为编译源,构建完成后临时目录随clean任务自动清理,完全隔离本地源码与编译用源码。

核心配置示例

android {
    sourceSets {
        main {
            java {
                // 添加临时目录为编译源,优先级高于原源码目录(按需调整)
                srcDirs += "build/generated/sources/customPlugin/java"
            }
        }
    }
}

// 自定义任务:处理注解并生成修改后的代码到临时目录
task generateModifiedCode(type: JavaExec) {
    classpath = sourceSets.main.runtimeClasspath
    // 自定义代码修改工具的主类,负责读取原源码、解析@WrapFunction注解、生成带try-catch的代码
    mainClass = "com.your.plugin.CodeModifier"
    // 传入原源码目录和临时输出目录参数
    args = ["src/main/java", "build/generated/sources/customPlugin/java"]
}

// 让编译前任务依赖代码生成任务
preBuild.dependsOn generateModifiedCode

方案2:Gradle Transform + ASM 字节码增强

核心思路

在编译流程的class文件生成后、打包前,通过自定义Gradle Transform介入,使用ASM框架直接修改字节码,无需触碰本地源码,对开发者完全无感知。

核心代码示例

自定义Transform类
class CustomWrapTransform extends Transform {
    @Override
    String getName() {
        return "customWrapTransform"
    }

    @Override
    Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS
    }

    @Override
    Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT
    }

    @Override
    boolean isIncremental() {
        return false
    }

    @Override
    void transform(TransformInvocation invocation) throws TransformException, InterruptedException, IOException {
        invocation.inputs.each { input ->
            // 处理jar包中的class文件
            input.jarInputs.each { jarInput ->
                def destJar = invocation.outputProvider.getContentLocation(
                    jarInput.name, jarInput.contentTypes, jarInput.scopes, Format.JAR
                )
                modifyJar(jarInput.file, destJar)
            }
            // 处理本地目录中的class文件
            input.directoryInputs.each { dirInput ->
                def destDir = invocation.outputProvider.getContentLocation(
                    dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY
                )
                modifyDirectory(dirInput.file)
                FileUtils.copyDirectory(dirInput.file, destDir)
            }
        }
    }

    private void modifyDirectory(File dir) {
        if (!dir.isDirectory()) return
        dir.eachFileRecurse { file ->
            if (file.name.endsWith(".class")) {
                modifyClassFile(file)
            }
        }
    }

    private void modifyClassFile(File file) {
        ClassReader cr = new ClassReader(new FileInputStream(file))
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
        ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
            @Override
            MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions)
                // 检查方法是否带有@WrapFunction注解
                AnnotationVisitor av = mv.visitAnnotation("Lcom/your/annotation/WrapFunction;", false)
                if (av != null) {
                    // 返回自定义MethodVisitor,插入try-catch逻辑
                    return new TryCatchInjectVisitor(Opcodes.ASM9, mv, access, name, descriptor)
                }
                return mv
            }
        }
        cr.accept(cv, ClassReader.SKIP_DEBUG)
        FileOutputStream fos = new FileOutputStream(file)
        fos.write(cw.toByteArray())
        fos.close()
    }

    private void modifyJar(File srcJar, File destJar) {
        // 处理jar包的逻辑,核心也是读取class文件后调用modifyClassFile修改
        JarFile jarFile = new JarFile(srcJar)
        JarOutputStream jos = new JarOutputStream(new FileOutputStream(destJar))
        Enumeration<JarEntry> entries = jarFile.entries()
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement()
            jos.putNextEntry(new JarEntry(entry.name))
            if (entry.name.endsWith(".class")) {
                byte[] classBytes = jarFile.getInputStream(entry).bytes
                ClassReader cr = new ClassReader(classBytes)
                ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
                ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
                    // 同modifyClassFile中的注解检查逻辑
                }
                cr.accept(cv, ClassReader.SKIP_DEBUG)
                jos.write(cw.toByteArray())
            } else {
                jos.write(jarFile.getInputStream(entry).bytes)
            }
            jos.closeEntry()
        }
        jarFile.close()
        jos.close()
    }
}
插入try-catch的MethodVisitor实现
public class TryCatchInjectVisitor extends MethodVisitor {
    private final Label tryStart = new Label();
    private final Label tryEnd = new Label();
    private final Label catchStart = new Label();

    public TryCatchInjectVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
        super(api, methodVisitor);
    }

    @Override
    public void visitCode() {
        // 标记try块开始
        mv.visitLabel(tryStart);
        super.visitCode();
    }

    @Override
    public void visitInsn(int opcode) {
        if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
            // 标记try块结束,进入catch块
            mv.visitLabel(tryEnd);
            mv.visitTryCatchBlock(tryStart, tryEnd, catchStart, "Ljava/lang/Exception;");
            mv.visitLabel(catchStart);
            // 存储异常对象到局部变量
            mv.visitVarInsn(Opcodes.ASTORE, 1);
            // 可添加自定义异常处理逻辑(如日志打印)
            mv.visitInsn(Opcodes.POP);
        }
        super.visitInsn(opcode);
    }
}

针对@WrapFunction注解场景的实现建议

场景需求

为带有@WrapFunction注解的函数自动添加try-catch块,编译时生效但不改动本地原代码。

原代码示例:

@WrapFunction
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    Log.e("TAG","Nello World")
}

编译后生效的逻辑:

@WrapFunction
override fun onCreate(savedInstanceState: Bundle?) {
   try{
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       Log.e("TAG","Nello World")
   }catch (ex:Exception){
       // 自定义异常处理逻辑
   }
}

方案选择

  • 若需修改源码级逻辑(如添加代码块、调整变量逻辑),优先用SourceSet + AST解析方案,可读性和可维护性更强
  • 若仅需方法增强(如添加try-catch、日志埋点),优先用Gradle Transform + ASM方案,性能更高,无需处理复杂源码语法

内容的提问来源于stack exchange,提问作者Farman Ullah Marwat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 14:35:37