从开发一个插件看,安卓gradle插件适配AGP8.0

transform API没学会?不用学了,AsmClassVisitorFactory更简单

    • 前言
    • 从零开始,构建一个兼容AGP8.0的插件
    • 插件发布
    • 为什么适配AGP8.0没用8.0.0版本?
    • 同一插件如何注册多个转换任务/顺序执行多个转换任务
    • InstrumentationParameters,插件配置参数
    • 更新
    • 总结
    • 实例代码
    • 参考链接

前言

相信很多小伙伴项目还没有升级AGP7.0,可是最新的AGP已经到8.2了,适配AGP8.0也要提上日程了,尤其是一些插件项目,因为8.0删除了transform API,所以需要提前做好适配工作。
如果你是一个插件小白,本篇可以教你从0开始在AGP7.0以上如何开发插件。
如果你是一个插件开发者,相信本篇也可以给你适配AGP8.0带来一些帮助。

从零开始,构建一个兼容AGP8.0的插件

首先我们新建一个空项目,然后在项目中开始添加模块。
由于as没有创建插件模块的选项,所以这里我们选择手动添加。
第一步:在app同级目录创建如下文件
从开发一个插件看,安卓gradle插件适配AGP8.0_第1张图片
然后在setting.gradle配置文件中引入插件

include ':app'
include ':plugin'

接着我们在插件目录的build.gradle文件中添加一些必要的依赖:

plugins {
   
    id 'java'
    id 'groovy'
    id 'kotlin'
}


dependencies {
   

    //gradle sdk
    implementation gradleApi()
    //groovy sdk
    implementation localGroovy()
    implementation 'com.android.tools.build:gradle:7.4.2'
    implementation 'com.android.tools.build:gradle-api:7.4.2'
    implementation 'org.ow2.asm:asm:9.1'
    implementation 'org.ow2.asm:asm-util:9.1'
    implementation 'org.ow2.asm:asm-commons:9.1'
}

大家可能注意到了,这里我们依赖的gradle版本并非8.0版本,而是gradle7.4.2版本,为啥不用8.0.0版本呢,这个稍后再解释,我们继续插件的创建。
接着我们开始添加插件的源文件:
从开发一个插件看,安卓gradle插件适配AGP8.0_第2张图片
在TestPlugin.properties配置中指定插件入口类,同时该配置文件的名称xxx.properties的xxx即为插件的名称,也就是后期我们应用引入该插件时的名称

implementation-class=com.cs.plugin.TestPlugin

这里还需要注意一点,就是创建META-INF.gradle-plugins的文件夹时,一定要创建两个文件夹,千万不要这样创建
从开发一个插件看,安卓gradle插件适配AGP8.0_第3张图片
从开发一个插件看,安卓gradle插件适配AGP8.0_第4张图片
接下来开始真正的插件代码逻辑了
TestPlugin中添加如下代码:

class TestPlugin  : Plugin<Project> {
   
    override fun apply(project: Project) {
   
        //这里appExtension获取方式与原transform api不同,可自行对比
        val appExtension = project.extensions.getByType(
            AndroidComponentsExtension::class.java
        )
        //这里通过transformClassesWith替换了原registerTransform来注册字节码转换操作
        appExtension.onVariants {
    variant ->
            //可以通过variant来获取当前编译环境的一些信息,最重要的是可以 variant.name 来区分是debug模式还是release模式编译
            variant.instrumentation.transformClassesWith(TimeCostTransform::class.java, InstrumentationScope.ALL) {
   
            }
            //InstrumentationScope.ALL 配合 FramesComputationMode.COPY_FRAMES可以指定该字节码转换器在全局生效,包括第三方lib
            variant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES)
        }
    }

}

这里我们注册一个TimeCostTransform的字节码转换功能,用来统计方法执行的时长。TimeCostTransform需要实现AsmClassVisitorFactory这个接口,该接口正是用于替换原Transform的API,新API中只需要关注ASM操作的实现即ClassVisitor,大大简化了插件开发的工作。
TimeCostTransform中添加如下代码

abstract class TimeCostTransform : AsmClassVisitorFactory<InstrumentationParameters.None> {
   
    override fun createClassVisitor(
        classContext: ClassContext,
        nextClassVisitor: ClassVisitor
    ): ClassVisitor {
   
        //指定真正的ASM转换器
        return TimeCostClassVisitor(nextClassVisitor)
    }

    //通过classData中的当前类的信息,用来过滤哪些类需要执行字节码转换,这里支持通过类名,包名,注解,接口,父类等属性来组合判断
    override fun isInstrumentable(classData: ClassData): Boolean {
   
        //指定包名执行
        return classData.className.startsWith("com.cs.supportagp80")
    }
}

接着我们创建一个TimeCostClassVisitor的字节码转换器,用来执行在方法开始时及结束时分别插入代码来统计方法耗时,并且打印出来的逻辑

class TimeCostClassVisitor(nextVisitor: ClassVisitor) : ClassVisitor(
    Opcodes.ASM5, nextVisitor) {
   

    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
   
        val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
        if (name == "" || name == "") {
   
            return methodVisitor
        }
        val newMethodVisitor =
            object : AdviceAdapter(Opcodes.ASM5, methodVisitor, access, name, descriptor) {
   
                private var startTimeLocal = -1 // 保存 startTime 的局部变量索引

                override fun visitInsn(opcode: Int) {
   
                    super.visitInsn(opcode)
                }

                @Override
                override fun onMethodEnter() {
   
                    super.onMethodEnter();
                    // 在onMethodEnter中插入代码 val startTime = System.currentTimeMillis()
                    mv.visitMethodInsn(
                        Opcodes.INVOKESTATIC,
                        "java/lang/System",
                        "currentTimeMillis",
                        "()J",
                        false
                    )
                    startTimeLocal = newLocal(Type.LONG_TYPE) // 创建一个新的局部变量来保存 startTime
                    mv.visitVarInsn(Opcodes.LSTO

你可能感兴趣的:(Android,gradle,android,gradle)