适配 AGP8.5 版本,字节码转换(二)

在这里插入图片描述

今日图片
时间:2025.02.14 周五
地点:深大地铁站

1. 转换注册

使用 onVariants 添加 transformClassesWith,不同点注意点(有些可能是对于 java 开发来说,突然转到 kotlin 有点不陌生)

  • AppExtension 变成了 AndroidComponentsExtension
  • registerTransform 变成了 onVariants transformClassesWith
  • ClassVisitor 变成了 AsmClassVisitorFactory 接口实现
  • ClassVisitor 构造方法直接传参数,变成了 parameters 成员实现
import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.AppPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project

class Check : Plugin<Project> {
    override fun apply(project: Project) {
        println("自定义 Check 插件运行 ================>")

        //在应用 AppPlugin 插件上注册回调
        project.plugins.withType(AppPlugin::class.java) {
            //获取 AndroidComponentsExtension,类似 AGP7 的
            //def appExtension = project.extensions.getByType(AppExtension)
            val androidComponentsExtension =
                project.extensions.getByType(AndroidComponentsExtension::class.java)
            androidComponentsExtension.onVariants { varients ->
                varients.instrumentation.transformClassesWith(
                    TestAsmClassVisitor::class.java,//指定 asm 操作类
                    InstrumentationScope.ALL//指定操作范围
                ) { params ->//指定操作参数,与之前通过 TestAsmClassVisitor 构造函数传参类似
                    params.processClassName.set("com.example.agp8kotlinplugin.MainActivity")
                    params.oldMethodName.set("sayHello")
                    params.newMethodName.set("sayHelloWorld")
                }
            }
        }
    }
}

2. 转换过滤

实现 AsmClassVisitorFactory接口,并重写重要方法

  • isInstrumentable:指定当前类是否需要转换

作用:主要目的是类过滤,明确那部分的类需要转换处理(实现 isInstrumentable 方法)

不同点是:

  • 实现高度抽象的 AsmClassVisitorFactory
  • 使用范型方式传参数,被抽象类成员 parameters 接收
  • 更加明确哪些类需要转换,实现接口 isInstrumentable
import com.android.build.api.instrumentation.AsmClassVisitorFactory
import com.android.build.api.instrumentation.ClassContext
import com.android.build.api.instrumentation.ClassData
import com.android.build.api.instrumentation.InstrumentationParameters
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.objectweb.asm.ClassVisitor

abstract class TestAsmClassVisitor : AsmClassVisitorFactory<TestAsmClassVisitor.TestParams> {

    override fun createClassVisitor(
        classContext: ClassContext, nextClassVisitor: ClassVisitor
    ): ClassVisitor {
        return ClassMethodVisitor(
            instrumentationContext.apiVersion.get(),
            nextClassVisitor,
            parameters.get().oldMethodName.get(),//parameters 输入的参数
            parameters.get().newMethodName.get(),
        )
    }


    //返回值表示输入的类是否需要转入下一步自定义的 classVisitor 处理
    override fun isInstrumentable(classData: ClassData): Boolean {
        if (classData.className == parameters.get().processClassName.get()) {
            println("找到需要处理的类:=${classData.className}")
            return true
        }
        return false
    }

    //自定义输入或输出参数,对应接口 AsmClassVisitorFactory 的 parameters 成员
    interface TestParams : InstrumentationParameters {

        @get:Input
        val processClassName: Property<String>

        @get:Input
        val oldMethodName: Property<String>

        @get:Input
        val newMethodName: Property<String>
    }
}

3. 转换实现

实现一个简单的需求:你需要自定义映射,混淆指定类里面的特定方法

你看到了我前面的转换输入参数:

明确指定类需要混淆(转换过滤找到指定类)
params.processClassName.set("com.example.agp8kotlinplugin.MainActivity")

明确特定方法混淆的映射(转换实现做混淆替换)
params.oldMethodName.set("sayHello")
params.newMethodName.set("sayHelloWorld")
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor

class ClassMethodVisitor(
    apiVersion: Int,
    cv: ClassVisitor,
    private val oldMethodName: String,
    private val newMethodName: String
) : ClassVisitor(apiVersion, cv) {
    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {

        println("当前类包含的方法=${name}")

        if (name == oldMethodName) {
            println("\t找到要处理的方法=${name},修改为=${newMethodName}")
            return super.visitMethod(access, newMethodName, descriptor, signature, exceptions)
        }
        return super.visitMethod(access, name, descriptor, signature, exceptions)
    }
}

运行输出

在这里插入图片描述

实际效果:sayHello 成功修改为映射后的 sayHelloWorld

在这里插入图片描述
在这里插入图片描述

4. 低版本对比

代码上看相比之前的 AGP4+ transform 确实简洁了很多,之前的 transform 方法重写需要关注的地方比较多:

  • transformInput.getJarInputs():需要处理 jar 文件
    • 拿到 jar 遍历读取里面的每个 element
    • 有各种不同类型的 element,还需要过滤 class 文件
    • 再确定当前 class 是否属于处理目标文件
    • 是目标文件,才以字节数组输入到具体 asm 操作转换
    • asm 修改完毕之后还要自己保存,否则下一个 transform 没有输入

  • transformInput.getDirectoryInputs():需要处理目录下的 class 文件
    • 需要遍历目录读取每个 class 文件
    • 再确定当前 class 是否属于处理目标文件
    • 是目标文件,才以字节数组输入到具体 asm 操作转换
    • asm 修改完毕之后还要自己保存,否则下一个 transform 没有输入

如果还是不同清楚 AGP8 之前的 transform,你可以查看之前的文章大概了解:AGP4 Transform 小结

AGP 低版本时候感觉确实麻烦

我觉得升级还是挺有必要的,升级往往带来的是效率的提升和各种优化

对你还可以接触新知识,何乐而不为

实际项目生产环境可能不会这么轻易升级,你应该有自驱力,主动接触新知识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值