Android Gradle 新旧两种自定义插件的完整步骤(建议收藏)

294 阅读3分钟

下面我将以 “在所有 Activity 的 onCreate 方法插入日志”为例,分别给出Transform API(老方案)和Instrumentation API(新方案,AGP 7.0+)的完整集成过程,包括插件工程结构、依赖、注册、应用、调试等所有关键步骤,确保你可以直接落地实践。


一、Transform API 完整集成过程(适用于 AGP < 7.0)

⚠️ 注意:Transform API 在 AGP 7.0+ 已废弃,不推荐新项目使用,仅供老项目参考。

1. 新建插件模块(如 activity-log-transform

目录结构示例:

activity-log-transform/
├── build.gradle
├── src/
│   └── main/
│       ├── java/
│       │   └── com/example/transform/
│       │       ├── ActivityLogTransform.java
│       │       └── ActivityLogTransformPlugin.java
│       └── resources/
│           └── META-INF/
│               └── gradle-plugins/
│                   └── activity-log-transform-plugin.properties

2. 配置插件模块依赖

activity-log-transform/build.gradle

plugins {
    id 'java'
}

dependencies {
    implementation 'com.android.tools.build:gradle:4.2.2' // 你的 AGP 版本
    implementation 'org.ow2.asm:asm:9.5'
    implementation 'org.ow2.asm:asm-commons:9.5'
}

3. 实现 Transform 插件

ActivityLogTransformPlugin.java

package com.example.transform;

import com.android.build.gradle.AppExtension;
import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class ActivityLogTransformPlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        project.getPlugins().withId("com.android.application", plugin -> {
            AppExtension android = project.getExtensions().getByType(AppExtension.class);
            android.registerTransform(new ActivityLogTransform());
        });
    }
}

ActivityLogTransform.java

package com.example.transform;

import com.android.build.api.transform.*;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;

import java.io.*;
import java.util.Collection;

public class ActivityLogTransform extends Transform {
    @Override
    public String getName() { return "ActivityLogTransform"; }

    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() {
        return Collections.singleton(QualifiedContent.DefaultContentType.CLASSES);
    }

    @Override
    public Set<QualifiedContent.Scope> getScopes() {
        return Collections.singleton(QualifiedContent.Scope.PROJECT);
    }

    @Override
    public boolean isIncremental() { return false; }

    @Override
    public void transform(TransformInvocation transformInvocation) throws IOException {
        for (TransformInput input : transformInvocation.getInputs()) {
            for (DirectoryInput dirInput : input.getDirectoryInputs()) {
                File dir = dirInput.getFile();
                if (dir.isDirectory()) {
                    for (File file : org.apache.commons.io.FileUtils.listFiles(dir, new String[]{"class"}, true)) {
                        byte[] bytes = org.apache.commons.io.FileUtils.readFileToByteArray(file);
                        ClassReader cr = new ClassReader(bytes);
                        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
                        ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
                            @Override
                            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                                return new AdviceAdapter(Opcodes.ASM9, mv, access, name, desc) {
                                    @Override
                                    protected void onMethodEnter() {
                                        if (name.equals("onCreate") && desc.startsWith("(Landroid/os/Bundle;)")) {
                                            mv.visitLdcInsn("Monitor");
                                            mv.visitLdcInsn("Activity onCreate: " + name);
                                            mv.visitMethodInsn(
                                                Opcodes.INVOKESTATIC,
                                                "android/util/Log",
                                                "d",
                                                "(Ljava/lang/String;Ljava/lang/String;)I",
                                                false
                                            );
                                            mv.visitInsn(Opcodes.POP);
                                        }
                                    }
                                };
                            }
                        };
                        cr.accept(cv, 0);
                        org.apache.commons.io.FileUtils.writeByteArrayToFile(file, cw.toByteArray());
                    }
                }
                // 输出到目标目录
                File dest = transformInvocation.getOutputProvider().getContentLocation(
                        dirInput.getName(), getInputTypes(), getScopes(), Format.DIRECTORY);
                org.apache.commons.io.FileUtils.copyDirectory(dir, dest);
            }
            // 还需处理 jarInput(省略,原理类似)
        }
    }
}

4. 注册插件

activity-log-transform/src/main/resources/META-INF/gradle-plugins/activity-log-transform-plugin.properties

implementation-class=com.example.transform.ActivityLogTransformPlugin

5. 在主工程中应用插件

app/build.gradle

plugins {
    id 'com.android.application'
    id 'activity-log-transform-plugin'
}

6. 构建并验证

  • 执行 ./gradlew assembleDebug
  • 运行 App,查看所有 Activity 的 onCreate 是否有日志输出

二、Instrumentation API 完整集成过程(AGP 7.0+ 推荐)

1. 新建插件模块(如 activity-log-instrumentation

目录结构示例:

activity-log-instrumentation/
├── build.gradle.kts
├── src/
│   └── main/
│       ├── kotlin/
│       │   └── com/example/instrumentation/
│       │       ├── ActivityLogInstrumentationPlugin.kt
│       │       └── ActivityLogClassVisitorFactory.kt
│       └── resources/
│           └── META-INF/
│               └── gradle-plugins/
│                   └── activity-log-instrumentation-plugin.properties

2. 配置插件模块依赖

activity-log-instrumentation/build.gradle.kts

plugins {
    `kotlin-dsl`
}

dependencies {
    implementation("com.android.tools.build:gradle:8.2.0") // 你的 AGP 版本
    implementation("org.ow2.asm:asm:9.5")
    implementation("org.ow2.asm:asm-commons:9.5")
}

3. 实现 Instrumentation 插件

ActivityLogInstrumentationPlugin.kt

package com.example.instrumentation

import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project

class ActivityLogInstrumentationPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.plugins.withId("com.android.application") {
            val androidComponents = project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java)
            androidComponents.onVariants { variant ->
                variant.instrumentation.transformClassesWith(
                    ActivityLogClassVisitorFactory::class.java,
                    InstrumentationScope.ALL
                ) { }
                variant.instrumentation.setAsmFramesComputationMode(
                    com.android.build.api.instrumentation.FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
                )
            }
        }
    }
}

ActivityLogClassVisitorFactory.kt

package com.example.instrumentation

import com.android.build.api.instrumentation.*
import org.objectweb.asm.*
import org.objectweb.asm.commons.AdviceAdapter

abstract class ActivityLogClassVisitorFactory : ClassVisitorFactory<InstrumentationParameters.None> {
    override fun createClassVisitor(classContext: ClassContext, nextClassVisitor: ClassVisitor): ClassVisitor {
        return object : ClassVisitor(Opcodes.ASM9, nextClassVisitor) {
            override fun visitMethod(
                access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
            ): MethodVisitor {
                val mv = super.visitMethod(access, name, desc, signature, exceptions)
                return object : AdviceAdapter(Opcodes.ASM9, mv, access, name, desc) {
                    override fun onMethodEnter() {
                        if (name == "onCreate" && desc.startsWith("(Landroid/os/Bundle;)")) {
                            mv.visitLdcInsn("Monitor")
                            mv.visitLdcInsn("Activity onCreate: $name")
                            mv.visitMethodInsn(
                                Opcodes.INVOKESTATIC,
                                "android/util/Log",
                                "d",
                                "(Ljava/lang/String;Ljava/lang/String;)I",
                                false
                            )
                            mv.visitInsn(Opcodes.POP)
                        }
                    }
                }
            }
        }
    }

    override fun isInstrumentable(classData: ClassData): Boolean {
        // 只处理 Activity 类
        return classData.superClasses.contains("android/app/Activity")
    }
}

4. 注册插件

activity-log-instrumentation/src/main/resources/META-INF/gradle-plugins/activity-log-instrumentation-plugin.properties

implementation-class=com.example.instrumentation.ActivityLogInstrumentationPlugin

5. 在主工程中应用插件

app/build.gradle.kts

plugins {
    id("com.android.application")
    id("activity-log-instrumentation-plugin")
}

6. 构建并验证

  • 执行 ./gradlew assembleDebug
  • 运行 App,查看所有 Activity 的 onCreate 是否有日志输出

三、调试与验证建议

  • 插桩代码可先用 printlnLog.d 输出,确认插件已生效。
  • 可用 javapBytecode Viewer 反编译 class 文件,验证字节码是否被修改。
  • Instrumentation API 支持 variant 粒度,可在 onVariants 里加条件只对特定 buildType/flavor 生效。

四、总结

  • Transform API 适合 AGP < 7.0,注册方式为 android.registerTransform,全局生效,需手动处理输入输出。
  • Instrumentation API 适合 AGP 7.0+,注册方式为 variant.instrumentation.transformClassesWith,variant 粒度,AGP 自动处理输入输出,类型安全,推荐新项目使用。
  • 插件模块建议独立于主工程,便于维护和复用。