news 2026/6/7 1:57:19

Gradle8.0中Transform的替代方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Gradle8.0中Transform的替代方案
一、路由插件设计思路

Transform在Gradle8.0中被移除了,从官方文档《Android Gradle 插件 API 更新》中可知并没有提供直接的替代Api,而是区分不同场景提供了几种对应的解决方案。在上篇文章《基于Gradle8.0的插件开发》中,基于Gradle8.0最新版本重新梳理了插件的使用方式和自定义流程,最后写了一个模拟页面打点统计的Demo来实现比较简单的基于“转换字节码”场景的插件开发。本文以路由信息收集为例继续阐述如何开发一个针对“基于整个程序分析的转换”场景的插件。该案例专注于插件开发的流程,而不会涉及到登录校验、服务发现、跳转拦截器等方面的内容,有兴趣的同学可以自行实现。

先定义一个注解,用来标记页面的路由信息,在定义一个路由管理类,用来存储路由信息并对外提供跳转功能。在工程中添加hui_aop模块,创建HuiRouterPath注解 和 HuiRouterApi路由管理类。在Gradle插件中对HuiRouterApi进行插桩,添加插入路由信息的字节码指令,在程序运行时,插桩的字节码指令执行,路由信息被注册到HuiRouterApi内部的路由表中,然后外部就可以通过HuiRouterApi进行跳转操作了。

​在工程的hui_plugin模块中,添加HuiRouterPlugin插件 和 HuiRouterTask任务,处理注解扫描、路由表信息收集,通过ASM向 HuiRouterApi类中插入生成路由表的字节码指令。修改后,所有输入的class 和 jar会重新生成中间产物classes.jar(全路径:gradle80-plugin-demo/app/build/intermediates/classes/debug/ALL/debugHuiRouterTask/classes.jar)传递给下个任务继续打包流程,最终打出一个可运行的APK包。

插件工作流程图:

二、路由插件实现流程

1、新建一个module模块hui_aop

2、定义注解HuiRouterPath用于标记路由信息

​@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) annotation class HuiRouterPath( val path: String = "" //路由表中的path )

3、定义路由管理类HuiRouterApi

object HuiRouterApi { ​ //存储跳转路由表 private val routerPathMap = mutableMapOf<String, String>() ​ /** * 注册跳转路由 */ private fun addRouterPath(key: String?, path: String?) { if (key != null && path != null) { routerPathMap[key] = path } } ​ /** * 页面跳转 */ fun routerPath(context: Context, routerTargetPath: String) { routerPathMap[routerTargetPath]?.takeIf { it.isNotEmpty() }?.run { context.startActivity(Intent(context, Class.forName(this))) } } ​ /** * 打印routerPathMap */ fun printRouterPathMap() { routerPathMap.forEach { entry -> Log.e("HuiRouterApi", "routerPathMap entry -> $entry") } } }

​4、定义一个Task任务类HuiRouterTask,将输入的jar文件和class文件进行插桩修改,并输出到output。扫描过程中,收集的路由信息记录到annotationPathMap中

abstract class HuiRouterTask : DefaultTask() { ​ //所有的jar文件输入信息 @get:InputFiles abstract val allJars: ListProperty<RegularFile> ​ //所有的class文件输入信息 @get:InputFiles abstract val allDirectories: ListProperty<Directory> ​ //经过插桩修改后的输出信息 @get:OutputFile abstract val output: RegularFileProperty ​ //注册带有HuiRouterPath注解的类 private val annotationPathMap = HashMap<String?, String?>() //HuiRouterApi的class对应的Jar包文件 private var routerApiJarFile: File? = null ​ @TaskAction fun taskAction() { //todo 遍历扫描class文件 } }

5、在taskAction方法中,扫描遍历所有的jar和class文件,收集路由信息,查找到HuiRouterApi类,并将收集到的路由信息通过插桩的方式添加到HuiRouterApi中

@TaskAction fun taskAction() { ​ //输出到output的流 val jarOutput = JarOutputStream( BufferedOutputStream(FileOutputStream(output.get().asFile)) ) ​ //遍历扫描class allDirectories.get().forEach { directory -> ...... ​ scanAnnotationClass(file.inputStream()) ​ ...... } ​ //遍历扫描jar allJars.get().forEach { jarInputFile -> ...... ​ if (jarEntry.name.equals("com/znh/aop/api/HuiRouterApi.class")) { routerApiJarFile = jarInputFile.asFile } else { scanAnnotationClass(jarFile.getInputStream(jarEntry)) } ​ ...... } ​ //对HuiRouterApi进行插桩修改,添加收集到的路由信息 routerApiJarFile?.let { transformJar(it, jarOutput) } ​ //关闭输出流 jarOutput.close() }

6、在scanAnnotationClass方法中,根据注解信息和目标类信息,收集路由信息到annotationPathMap中

/** * 扫描有目标注解的类 * * @param inputStream */ private fun scanAnnotationClass(inputStream: InputStream) { val classReader = ClassReader(inputStream) val classNode = ClassNode() classReader.accept(classNode, ClassReader.EXPAND_FRAMES) val annotations = classNode.invisibleAnnotations //获取声明的所有注解 if (annotations != null && annotations.isNotEmpty()) { annotations.forEach { aNode -> if ("Lcom/znh/aop/annotation/HuiRouterPath;" == aNode.desc) { var pathKey = classNode.name if (aNode.values != null && aNode.values.size > 1) { pathKey = aNode.values[1] as? String } annotationPathMap[pathKey] = classNode.name.replace("/", ".") } } } inputStream.close() }

7、在transformJar方法中,将搜集到的annotationPathMap中的路由信息,通过ASM插桩操作,调用HuiRouterApi的addRouterPath方法添加进去。具体的插桩操作可查看Demo中的HuiRouterCVisitor和HuiRouterMVisitor,其中核心的ASM字节码指令操作如下

override fun onMethodExit(opcode: Int) { super.onMethodExit(opcode) annotationPathMap?.forEach { entry -> mv.visitFieldInsn( GETSTATIC, "com/znh/aop/api/HuiRouterApi", "INSTANCE", "Lcom/znh/aop/api/HuiRouterApi;" ) mv.visitLdcInsn(entry.key) mv.visitLdcInsn(entry.value) mv.visitMethodInsn( INVOKEVIRTUAL, "com/znh/aop/api/HuiRouterApi", "addRouterPath", "(Ljava/lang/String;Ljava/lang/String;)V", false ) println("HuiRouterApi -> addRouterPath插入:$entry") } }

​8、在HuiRouterPlugin的apply方法中注册HuiRouterTask任务

class HuiRouterPlugin : Plugin<Project> { override fun apply(target: Project) { val androidComponents = target.extensions.getByType(AndroidComponentsExtension::class.java) androidComponents.onVariants { variant -> val taskProvider = target.tasks.register(//注册HuiRouterTask任务 "${variant.name}HuiRouterTask", HuiRouterTask::class.java ) variant.artifacts.forScope(ScopedArtifacts.Scope.ALL) //扫描所有class .use(taskProvider) .toTransform( type = ScopedArtifact.CLASSES, inputJars = HuiRouterTask::allJars, inputDirectories = HuiRouterTask::allDirectories, into = HuiRouterTask::output ) } } }

9、在hui_pulgin模块的build.gradle.kts中的gradlePlugin中,扩展一个对HuiRouterPlugin插件的配置

create("routerPlugin") { group = "com.znh.plugin" version = "1.0.1" id = "com.znh.plugin.router" implementationClass = "com.znh.plugin.router.HuiRouterPlugin" }

10、将插件发布到本地maven中,然后使用插件id “com.znh.plugin.router” 应用到项目中,具体使用方式跟Page插件一样。然后为页面定义路由,进行跳转测试

//定义页面路由常量 object HuiConstants { const val ROUTER_PATH_DEMO = "path/app/DemoActivity" const val ROUTER_PATH_FIRST = "path/app/FirstActivity" const val ROUTER_PATH_SECOND = "path/app/SecondActivity" const val ROUTER_PATH_MODULE1 = "path/module1/Module1Activity" } ​ //DemoActivity路由配置 @HuiRouterPath(HuiConstants.ROUTER_PATH_DEMO) class DemoActivity : ComponentActivity() { ​ //FirstActivity路由配置 @HuiRouterPath(HuiConstants.ROUTER_PATH_FIRST) class FirstActivity : ComponentActivity() { //SecondActivity路由配置 @HuiRouterPath(HuiConstants.ROUTER_PATH_SECOND) class SecondActivity : ComponentActivity() { ​ //Module1Activity路由配置 @HuiRouterPath(HuiConstants.ROUTER_PATH_MODULE1) class Module1Activity : ComponentActivity() {

11、在编译时和运行时可查看日志输出

//编译时日志输出 > Task :app:debugHuiRouterTask HuiRouterApi的静态代码块执行了 HuiRouterApi -> addRouterPath插入:path/module1/Module1Activity=com.znh.gradle80.plugin.module1.Module1Activity HuiRouterApi -> addRouterPath插入:path/app/SecondActivity=com.znh.gradle80.plugin.demo.SecondActivity HuiRouterApi -> addRouterPath插入:path/app/FirstActivity=com.znh.gradle80.plugin.demo.FirstActivity HuiRouterApi -> addRouterPath插入:path/app/DemoActivity=com.znh.gradle80.plugin.demo.DemoActivity ​ ​ //运行时log日志输出 HuiRouterApi com.znh.gradle80.plugin.demo E routerPathMap entry -> path/module1/Module1Activity=com.znh.gradle80.plugin.module1.Module1Activity HuiRouterApi com.znh.gradle80.plugin.demo E routerPathMap entry -> path/app/SecondActivity=com.znh.gradle80.plugin.demo.SecondActivity HuiRouterApi com.znh.gradle80.plugin.demo E routerPathMap entry -> path/app/FirstActivity=com.znh.gradle80.plugin.demo.FirstActivity HuiRouterApi com.znh.gradle80.plugin.demo E routerPathMap entry -> path/app/DemoActivity=com.znh.gradle80.plugin.demo.DemoActivity

12、页面路由配置好后,就可以通过HuiRouterApi的routerPath进行页面跳转了

//跳转到FirstActivity页面 HuiRouterApi.routerPath(this, HuiConstants.ROUTER_PATH_FIRST) ​ //跳转到SecondActivity页面 HuiRouterApi.routerPath(this, HuiConstants.ROUTER_PATH_SECOND) ​ //跳转到Module1Activity页面 HuiRouterApi.routerPath(this, HuiConstants.ROUTER_PATH_MODULE1) ​ //跳转到DemoActivity页面 HuiRouterApi.routerPath(this, HuiConstants.ROUTER_PATH_DEMO)
Demo地址:

GitHub - huihuigithub/blog_demo_projects: 记录平时学习中写的一些demo(gradle80-plugin-demo)

参考文档
  1. Android Gradle 插件 API 更新:Android Gradle 插件 API 更新 | Android Studio | Android Developers
  2. Android Gradle 插件版本说明:Android Gradle 插件 8.4 版本说明 | Android Studio | Android Developers
  3. 官方Demo:gradle-recipes/Kotlin/modifyProjectClasses/app/build.gradle.kts at agp-7.4 · android/gradle-recipes · GitHub

转自:Gradle8.0中Transform的替代方案_gradle transform-CSDN博客

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/5 0:52:02

16、Unix系统负载监控:命令与脚本详解

Unix系统负载监控:命令与脚本详解 1. 引言 在Unix系统中,准确监控系统负载对于保障系统的稳定运行至关重要。不同的命令如 iostat 、 sar 、 vmstat 和 uptime 等,都能从不同角度提供系统负载的相关信息。本文将详细介绍这些命令的语法、输出特点以及如何通过脚本…

作者头像 李华
网站建设 2026/6/6 17:46:54

3分钟掌握ant-design-x-vue:构建智能对话界面的完整指南

3分钟掌握ant-design-x-vue&#xff1a;构建智能对话界面的完整指南 【免费下载链接】ant-design-x-vue Ant Design X For Vue.&#xff08;WIP&#xff09; 疯狂研发中&#x1f525; 项目地址: https://gitcode.com/gh_mirrors/an/ant-design-x-vue 还在为开发AI对话界…

作者头像 李华
网站建设 2026/6/3 6:12:28

⭐力扣刷题:最长递增子序列

题目&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列…

作者头像 李华
网站建设 2026/6/3 11:40:40

家政系统源码解析:一体化解决方案如何重塑同城保洁+维修服务生态!

在同城生活服务数字化转型的浪潮中&#xff0c;家政服务行业正面临 “服务分散、流程繁琐、管理低效” 的瓶颈。亿坊家政系统源码作为一体化解决方案的标杆&#xff0c;凭借对保洁、维修、预约上门等核心场景的深度适配&#xff0c;打通 “用户 - 服务人员 - 商户 - 平台” 全链…

作者头像 李华
网站建设 2026/6/4 14:51:50

新能源知识库(162)高镍三元锂电池介绍

一、定义 “高镍三元锂电池”仍属于镍钴锰&#xff08;NCM&#xff09;或镍钴铝&#xff08;NCA&#xff09;体系&#xff0c;只是将正极中镍的摩尔分数提高到 ≥60%&#xff0c;典型代表有 NCM-622、NCM-811、NCA-90 等&#xff1b;普通三元锂电池多指 NCM-523 及以下镍含量的…

作者头像 李华