android2024 gradle8 Processor和ksp两种编译时注解实现

news2024/11/24 0:19:53

android编译时注解,老生常谈,外面的例子都是bindView,脑壳看疼了,自己学习和编写下。
而且现在已经进化到kotlin2.0,google也逐渐放弃kapt,进入维护状态。所以要好好看看本贴。
参考我的工程:
https://github.com/jzlhll/AndroidComponts
ClassNameAnnotations
ClassNameAnnotations-compiler
ClassNameAnnotations-ksp
app
四个模块。

一、编写kapt(abstractProcessor)

1. 新建注解的模块,注意是java/kotlin library:

请添加图片描述
配置build.gradle:

plugins {
    id 'java-library'
    id 'kotlin'
}

java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

添加自定义注解的java类:

@Retention(RetentionPolicy.CLASS)
@Target(value = ElementType.TYPE)
public @interface EntroFrgName {
}

这是我的需求,目的就是标记一个类,用来收集所有标注了注解的类,把他们收集成一个List。

2.再创建compiler模块,也是java/kotlin library:

请添加图片描述
得到2个模块。

2.1 gradle:
plugins {
    id 'java-library'
    id 'kotlin'
}

java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

dependencies {
    implementation project(':ClassNameAnnotations')
}
2.2 配置解析器辅助文件:

这一步可以通过autoservice来配置。查看文章末尾注意事项。
在main下面reosurces/META-INF/services/目录下,创建文件javax.annotation.processing.Processor
里面写上com.au.learning.classnamecompiler.MyProcessor ,
就是下面代码MyProcessor 的类路径。

2.3 编写注解解析代码:

class MyProcessor : AbstractProcessor() {
    private var processingEnv:ProcessingEnvironment? = null

    override fun init(processingEnv: ProcessingEnvironment?) {
        super.init(processingEnv)
        this.processingEnv = processingEnv
        processingEnv?.messager?.printMessage(Diagnostic.Kind.WARNING, "init...!")
    }

    /**
     * 所支持的注解合集
     */
    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        return mutableSetOf(EntroFrgName::class.java.canonicalName)
    }

    private fun isElementInAnnotations(target:Element, annotations: Set<TypeElement>) : Boolean {
        for (annotation in annotations) {
            //匹配注释
            if (target == annotation) {
                return true
            }
        }
        return false
    }

    //Element代表程序中的包名、类、方法。即注解所支持的作用类型。
    fun getMyElements(annotations: Set<TypeElement>, elements: Set<Element?>): Set<TypeElement> {
        val result: MutableSet<TypeElement> = HashSet()
        //遍历包含的 package class method
        for (element in elements) {
            //匹配 class or interface
            if (element is TypeElement) {
                for (annotationMirror in element.annotationMirrors) {
                    val found = isElementInAnnotations(annotationMirror.annotationType.asElement(), annotations)
                    if (found) {
                        result.add(element)
                        break
                    }
                }
            }
        }
        return result
    }

    /**
     * @param annotations 需要处理的注解 即getSupportedAnnotationTypes被系统解析得到的注解
     * @param roundEnv 注解处理器所需的环境,帮助进行解析注解。
     */
    override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?): Boolean {
        val elements = roundEnv?.rootElements?.let {
            if (annotations != null) {
                getMyElements(annotations, it)
            } else {
                null
            }
        }

        val names = AllEntroFragmentNamesTemplate()
        if (!elements.isNullOrEmpty()) {
            for (e in elements) {
                names.insert(e.qualifiedName.toString())
            }

            val code = names.end()
            processingEnv.filer?.let {
                try {
                    // 创建一个JavaFileObject来表示要生成的文件
                    val sourceFile: JavaFileObject = it.createSourceFile("com.allan.androidlearning.EntroList", null)
                    sourceFile.openWriter().use { writer ->
                        // 写入Java(或Kotlin)代码
                        writer.write(code)
                        writer.flush()
                    }
                } catch (e: IOException) {
                    processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate file: " + e.message)
                }
            }
        }

        return true
    }

    //一定要修改这里,避免无法生效
    override fun getSupportedSourceVersion(): SourceVersion {
        return SourceVersion.latestSupported()
    }
}


class AllEntroFragmentNamesTemplate : AbsCodeTemplate() {
    private val insertCode = StringBuilder()

    /**
     * com.allan.androidlearning.activities.LiveDataFragment.class
     */
    fun insert(javaClass:String) {
        insertCode.append("list.add(").append(javaClass).append(".class);").appendLine()
    }

    fun end() : String {
        return codeTemplate.replace("//insert001", insertCode.toString())
    }

    override val codeTemplate = """
package com.allan.androidlearning;

import androidx.fragment.app.Fragment;

import java.util.ArrayList;
import java.util.List;

public class EntroList {
    public List<Class<? extends Fragment>> getEntroList() {
        List<Class<? extends Fragment>> list = new ArrayList<>();
        //insert001
        return list;
    }
}
    """.trimIndent()
}

这里有2个可以进一步学习的东西,一是auto库帮你生成META-INF文件。
二是通过javapoet来生成文件。详细在文章末尾注意事项。
本质上APT的目的就是将未知的代码,写成一个具体的类,被现有代码去调用,我自然可以直接写出这个类。所以,我为了方便和减少学习成本,自行整了一个模版代码(这个模版代码可以自己写好一个类,拷贝到string codeTemplate),把生成部分通过string.replace处理即可。然后简单地通过processingEnv.filer.createSourceFile,write就可以完成,自认为是一个不错的办法。

3. 主工程

剩下就简单了,app/build.gradle修改:

	plugins {
	    id 'com.android.application'
	    id 'org.jetbrains.kotlin.android'
	    id 'kotlin-kapt' //添加
	}
	...
    //注解引如
    implementation project(':ClassNameAnnotations')
    //kotlin
    kapt project(':ClassNameAnnotations-compiler')
    //java工程换成annotationProcessor 
    //annotationProcessor project(':ClassNameAnnotations-compiler')

给代码添加自己的注解了:

@EntroFrgName
class CanvasFragment : ViewFragment() {

@EntroFrgName
class DialogsFragment : ViewFragment() {

编译:
调试过程,可以选择gradle->Tasks->other->kaptDebugKotlin来编译。比直接编译更快,更单一。
编译结果在:
请添加图片描述
再最后,把这个类,拿去类似BuildConfig一样去调用了。至此已经完成。

二、app模块是java工程

自然是用不了ksp的。
唯一修改是app/build.gradle:

    //java工程换成annotationProcessor 
    annotationProcessor project(':ClassNameAnnotations-compiler')

然后各个gradle中,无需kotlin相关的痕迹。略。

三、KSP

终于谈到ksp了。
跟上面kapt一样,创建2个java/kotlin的模块。一个注解模块,一个处理模块,(那个灰色的compiler代表着settings.gradle已经不加载,不使用,不管它)。
请添加图片描述
注解模块的注解可以使用kotlin的注解类,也可以继续使用java的注解类。
区别只是在provider的解析代码上有一点点区别:

//EntroFrgName是java的注解类
resolver.getSymbolsWithAnnotation(EntroFrgName::class.java.canonicalName)
//EntroFrgName是kotlin的注解类
resolver.getSymbolsWithAnnotation(EntroFrgName::class.qualifiedName!!)
1. gradle:
根目录的build.gradle添加:
plugins {
    id 'com.android.application' version '8.4.2' apply false
    id 'com.android.library' version '8.4.2' apply false
    id 'org.jetbrains.kotlin.android' version "1.9.24" apply false
    id 'com.google.devtools.ksp' version '1.9.24-1.0.20' apply false
}
ksp模块的build.gradle为:
plugins {
    id 'java-library'
    id 'kotlin'
}

java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

dependencies {
    implementation project(':ClassNameAnnotations')
    implementation('com.google.devtools.ksp:symbol-processing-api:1.9.24-1.0.20')
}

注意kotlin.android, devtools.ksp与symbol-processing-api三者的版本对应,查看https://github.com/google/ksp/releases。

2. 配置解析器辅助文件:

src/main/resources/META-INF/services/目录下:
com.google.devtools.ksp.processing.SymbolProcessorProvider 文件。写下如下的名字。
com.au.learning.classnamecompiler.AllEntroFrgNamesProvider。就是下面的类名。

3. provider解析代码:
class AllEntroFrgNamesProvider : SymbolProcessorProvider{
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
        return TestKspSymbolProcessor(environment)
    }
}

/**
 * creator: lt  2022/10/20  lt.dygzs@qq.com
 * effect : ksp处理程序
 * warning:
 */
class TestKspSymbolProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor {
    // 使用一个集合来跟踪已经处理过的符号
    private val processedSymbols = mutableSetOf<KSDeclaration>()

    override fun process(resolver: Resolver): List<KSAnnotated> {
        environment.logger.warn("process start....")

        val symbols = resolver.getSymbolsWithAnnotation(EntroFrgName::class.java.canonicalName)
        val ret = mutableListOf<KSAnnotated>()

        val allEntroFragmentNamesTemplate = AllEntroFragmentNamesTemplate()
        var hasMy = false

        symbols.toList().forEach { symbol->
            if (!symbol.validate())
                ret.add(symbol)
            else {
                if (symbol is KSClassDeclaration && symbol.classKind == ClassKind.CLASS) {
                    val qualifiedClassName = symbol.qualifiedName?.asString()
                    allEntroFragmentNamesTemplate.insert(qualifiedClassName!!)
                    hasMy = true
//                    symbol.accept(TestKspVisitor(environment), Unit)//处理符号
                } else {
                    ret.add(symbol)
                }
            }
        }

        if (hasMy) {
            val code = allEntroFragmentNamesTemplate.end()

            // 生成文件
            val file = environment.codeGenerator.createNewFile(
                dependencies = Dependencies(false),
                packageName = "com.allan.androidlearning",
                fileName = "EntroList"
            )

            // 写入文件内容
            OutputStreamWriter(file).use { writer ->
                writer.write(code)
            }
        }

        //返回无法处理的符号
        return ret
    }
}
4. 主工程app引入

类似前面kapt的,主工程app/build.gradle

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'com.google.devtools.ksp'
}

   implementation project(':ClassNameAnnotations')
   ksp project(':ClassNameAnnotations-ksp')

添加注解,编译后,最终生成的代码在:
请添加图片描述

注意事项

1. 注意点

1.1 打印日志用warn。android studio编译是默认不打印低级别的。

//Processor
processingEnv?.messager?.printMessage(Diagnostic.Kind.WARNING, "init...!")
//ksp
environment.logger.warn("process start....")

1.2 kapt已经逐渐放弃,kt2.0开始不再努力维护kapt。尽量迁移ksp。更快更有支持。

1.3 很多人使用glide,经常把kapt,annotationProcessor,ksp搞混。
我们可以看到,glide库:
请添加图片描述
它也是有2个process的模块的,一个是给老的kapt或者java(annotationProcessor)处理。一个是给ksp。我们如出一辙。

2. 进一步学习

第一个:
使用autoservice来自动注解MyProcessor ,让它帮我们生成META-INF里面的文件。这个autoservice就干这么点点事情。compiler这个模块添加gradle(自己在这里看最新版本,https://github.com/google/auto):

 annotationProcessor 'com.google.auto.service:auto-service:1.11.0'
 implementation 'com.google.auto.service:auto-service-annotations:1.11.0'

然后给我们的Processor类添加上注解:

@AutoService(value = {Processor.class})

这纯属于是,我还没有编写完自己的注解, 就已经使用上别的注解来给我的注解模块生成文件了。[手动狗头]。

第二个,使用javapoet来实现生成代码。需要自行了解他的api和class,函数的结构。有点学习成本。

3. 坑了一天

出现一个问题,始终找不到原因。原来是
请添加图片描述
META-INF下面是目录services,再放一个文件。之前搞成了META-INF.services这个错误的目录!
而studio中显示的却跟包名一样。导致ksp的时候,搞了好久一直编译不过,提示[ksp] No providers found in processor classpath。好在有这句话,终于在ksp下解决了,之后反推到kapt也解决了。之前搞kapt,怎么都搞不好,也没有提示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1898626.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国信华源全力守护湖南水库安全,汛期坚守岗位

连日来&#xff0c;湖南省遭受持续强降雨侵袭&#xff0c;部分地区暴雨倾盆&#xff0c;山塘河库水位急剧上升&#xff0c;防汛形势极为严峻。面对这场防汛大考&#xff0c;北京国信华源公司迅速响应&#xff0c;技术团队以高度的责任感和使命感&#xff0c;全力投入防汛减灾工…

2007年下半年软件设计师【上午题】试题及答案

文章目录 2007年下半年软件设计师上午题--试题2007年下半年软件设计师上午题--答案2007年下半年软件设计师上午题–试题

WEB04MyBatis

Mybatis mybatis查询 准备 准备工作 在目前的数据库中添加一张数据表emp 将资料中提供的day04-01-mybatis导入的目前的工程中 修改配置文件中的数据库的账户和密码 观察实体类中的属性和数据表中的字段的对应关系 查询结果封装 查询所有 SQL语句 select * from emp; …

【源码+文档+调试讲解】智能仓储系统 JSP

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;智能仓储系统当然也不能排除在外。智能仓储系统是以实际运用为开发背景&#xff0c;运用软件工程开发方法&#xff0c;采…

昇思MindSpore学习总结九——FCN语义分割

1、语义分割 图像语义分割&#xff08;semantic segmentation&#xff09;是图像处理和机器视觉技术中关于图像理解的重要一环&#xff0c;AI领域中一个重要分支&#xff0c;常被应用于人脸识别、物体检测、医学影像、卫星图像分析、自动驾驶感知等领域。 语义分割的目的是对图…

简单的git pull fail Can‘t update has no tracked branch解决记录

简单的git pull fail Can‘t update has no tracked branch解决记录 1. 问题描述 上午同事使用idea拉取代码的时候&#xff0c;发现拉取不了&#xff0c;提示用户权限问题&#xff0c;之后修改了git用户信息&#xff0c;发现还是拉取不了分支代码&#xff0c;然后删除了git r…

[pwn]静态编译

静态编译 1. 栈足够大的情况下 程序在ida打开后&#xff0c;左侧的函数栏目没有红色&#xff08;系统调用的函数&#xff09;&#xff0c;而只有一些静态函数&#xff0c;通常这类文件的大小会必普通的pwn题程序要大得多。 这种静态编译的题没有调用库函数&#xff0c;也就没…

nginx部署多个项目;vue打包项目部署设置子路径访问;一个根域名(端口)配置多个子项目

本文解决&#xff1a; vue打包项目部署设置子路径访问&#xff1b;nginx部署多个子项目&#xff1b;一个ip/域名 端口 配置多个子项目&#xff1b;配置后&#xff0c;项目能访问&#xff0c;但是刷新页面就丢失的问题 注&#xff1a;本文需要nginx配置基础。基础不牢的可见文…

springboot + mybatis 多数据源切换

参考的b站博主写的 配置文件: spring:datasource:db1:jdbc-url: jdbc:mysql://localhost:3306/interview_database?useUnicodetrue&characterEncodingutf-8&useSSLfalseusername: rootpassword: 12345driver-class-name: com.mysql.cj.jdbc.Driverdb2:jdbc-url: jdbc…

gda动态调试-cnblog

忽的发现gda有动态调试功能 动态监听返回值 框柱指定方法&#xff0c;选择调试方法&#xff0c;gda会自动监听函数的返回值&#xff0c;例如 自定义frida脚本 gda会自动生成hook该函数的frida脚本

RH850系列芯片深度剖析 1.8-内存管理之MPU

RH850系列芯片深度剖析 1.8-内存管理之MPU 文章目录 RH850系列芯片深度剖析 1.8-内存管理之MPU一、MPU简介1.1 功能特性1.2 系统保护标识符(SPID)二、保护区域设置2.1 保护区域属性设置2.2 保护区域设置注意事项2.2.1 跨越保护区域边界2.2.2 无效的保护区域设置2.2.3 保护违规…

LeetCode热题100刷题4:76. 最小覆盖子串、239. 滑动窗口最大值、53. 最大子数组和、56. 合并区间

76. 最小覆盖子串 滑动窗口解决字串问题。 labuladong的算法小抄中关于滑动窗口的算法总结&#xff1a; class Solution { public:string minWindow(string s, string t) {unordered_map<char,int> need,window;for(char c : t) {need[c];}int left 0, right 0;int …

LeetCode 算法:二叉树的最近公共祖先 III c++

原题链接&#x1f517;&#xff1a;二叉树的最近公共祖先 难度&#xff1a;中等⭐️⭐️ 题目 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点…

Streaming local LLM with FastAPI, Llama.cpp and Langchain

题意&#xff1a; 使用FastAPI、Llama.cpp和Langchain流式传输本地大型语言模型 问题背景&#xff1a; I have setup FastAPI with Llama.cpp and Langchain. Now I want to enable streaming in the FastAPI responses. Streaming works with Llama.cpp in my terminal, but…

Android车载开发中调试app与bat结合的丝滑小妙招

项目场景&#xff1a; 做Android车载的小伙伴调试app的时候常年就是手动adb命令三连&#xff0c;例如我常用的adb推送apk的命令 adb root adb remount adb push D:\workspace_atc\XSP3-10A\AutoSystemUIPlugin\app\release\CarSystemUI.apk /system/priv-app/CarSystemUI …

【Linux进程】进程优先级 Linux 2.6内核进程的调度

前言 进程是资源分配的基本单位, 在OS中存在这很多的进程, 那么就必然存在着资源竞争的问题, 操作系统是如何进行资源分配的? 对于多个进程同时运行, 操作系统又是如何调度达到并发呢? 本文将以Linux kernel 2.6为例 , 向大家介绍进程在操作系统中 (OS) 的调度原理; 1. 进程优…

什么是网络抓取|常见用例和问题

你可能听说过数据被称为现代信息社会的新石油。由于线上信息量庞大&#xff0c;能够有效地收集和分析网页数据已经成为企业、研究人员和开发人员的关键技能。这就是网页抓取技术的用武之地。网页抓取&#xff0c;也称为网页数据提取&#xff0c;是一种强大的技术&#xff0c;能…

国际上备考所有AWS云计算/IT证书的五大优质免费课程网站

最近越来越多的小伙伴来问小李哥&#xff0c;小李哥亚马逊云科技AWS认证大满贯是在哪里上课复习的呢&#xff1f;全部上付费课程那不是一笔巨款吗&#xff1f;小李哥这次来盘点备考国际上IT证书的5大优质免费课程网站(不只是亚马逊云科技AWS的课程&#xff0c;其他课程同样可以…

46.修复HOOK对代码造成的破坏

上一个内容&#xff1a;45.使用hook点链表实现指定跳转 以 45.使用hook点链表实现指定跳转 它的代码为基础进行修改 此代码已实现无敌与秒杀功能 HOOKPOINT.h文件里的修改 #pragma oncetypedef struct CPUINFO {unsigned eflags;unsigned edi;unsigned esi;unsigned ebp;un…

【面向就业的Linux基础】从入门到熟练,探索Linux的秘密(十)-git(2)

下面是一些git的常用命令和基本操作&#xff0c;可以当做平常的笔记查询&#xff0c;用于学习&#xff01;&#xff01;&#xff01; 文章目录 前言 一、git 二、git常用命令 总结 前言 下面是一些git的常用命令和基本操作&#xff0c;可以当做平常的笔记查询&#xff0c;用于…