当前位置:   article > 正文

Gradle8.0中Transform的替代方案_gradle 8.0 transform

gradle 8.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
)
  • 1
  • 2
  • 3
  • 4
  • 5

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")
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

​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文件
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

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()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

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()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

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")
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

​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
                )
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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() {
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

​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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Demo地址:

https://github.com/huihuigithub/blog_demo_projects(gradle80-plugin-demo)

参考文档

  1. Android Gradle 插件 API 更新:https://developer.android.google.cn/studio/releases/gradle-plugin-api-updates?hl=zh_cn
  2. Android Gradle 插件版本说明:https://developer.android.google.cn/studio/releases/gradle-plugin?hl=zh-cn#updating-plugin
  3. 官方Demo:https://github.com/android/gradle-recipes/blob/agp-7.4/Kotlin/modifyProjectClasses/app/build.gradle.kts

【个人微信公众号】
专注于Android开发领域技术分享
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/288511?site
推荐阅读
相关标签
  

闽ICP备14008679号