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

文章介绍了如何在AGP8.0中开发兼容的插件,由于transformAPI已被废弃,重点讲述了如何使用AsmClassVisitorFactory进行字节码转换。通过示例代码,作者展示了创建插件、注册转换任务和配置参数的新方法,同时也讨论了多个转换任务的执行顺序和配置参数的实时更新问题。
摘要由CSDN通过智能技术生成

前言

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

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

首先我们新建一个空项目,然后在项目中开始添加模块。
由于as没有创建插件模块的选项,所以这里我们选择手动添加。
第一步:在app同级目录创建如下文件
创建插件文件夹
然后在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版本呢,这个稍后再解释,我们继续插件的创建。
接着我们开始添加插件的源文件:
添加源文件
在TestPlugin.properties配置中指定插件入口类,同时该配置文件的名称xxx.properties的xxx即为插件的名称,也就是后期我们应用引入该插件时的名称

implementation-class=com.cs.plugin.TestPlugin

这里还需要注意一点,就是创建META-INF.gradle-plugins的文件夹时,一定要创建两个文件夹,千万不要这样创建
在这里插入图片描述
在这里插入图片描述
接下来开始真正的插件代码逻辑了
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 == "<clinit>" || name == "<init>") {
   
            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
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值