android ksp 注解处理器

概述

KSP (Kotlin Symbol Processing)是以 Kotlin 优先的 kapt 替代方案。KSP 可直接分析 Kotlin 代码,使得速度提高多达 2 倍。此外,它还可以更好地了解 Kotlin 的语言结构。

注意:ksp和kapt(apt)一样,都只能生成新代码文件,无法修改源代码

接下来通过实践,看看ksp api是如何使用的。

具体代码见 github.com/ccnio/Wareh...

ksp api 使用

功能:对一个使用了 ExtractorInterface 注解的类生成这个类的接口。

kotlin 复制代码
@ExtractorInterface("IBClass")
class BClass {
    fun funB() {

    }
}

// 生成 IBClass.kt
public interface IBClass {
  public fun funB(): Unit
}
  1. 创建一个 kotlin libray,build.gradle.kts 配置如下:
scss 复制代码
plugins {
    //org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "org-jetbrains-kotlin-jvm" }
    alias(libs.plugins.org.jetbrains.kotlin.jvm)
}

dependencies {
    implementation(libs.symbol.processing.api)//引入ksp
    implementation(libs.kotlinpoet)
    implementation(libs.kotlinpoet.ksp)
}
  1. 新建 SymbolProcessorProvider、SymbolProcessorEnvironment 并注册

创建TestKsp.kt,修改如下代码:

kotlin 复制代码
class ExtractInterfaceProcessorProvider : SymbolProcessorProvider {
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
        return ExtractInterfaceProcessor(
            environment.options,
            environment.logger,
            environment.codeGenerator
        )
    }
}


class ExtractInterfaceProcessor(
    private val options: Map<String, String>,
    private val logger: KSPLogger,
    private val codeGenerator: CodeGenerator
) : SymbolProcessor {
    override fun process(resolver: Resolver): List<KSAnnotated> {
        resolver.getSymbolsWithAnnotation(ExtractorInterface::class.qualifiedName.toString())
            .filterIsInstance<KSClassDeclaration>()
            .forEach(::generateInterface) // 具体的实现参见 github
        return emptyList()
    }
}

// 实际开发中 注解相关内容会在单独的一个 library
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class ExtractorInterface(val name: String)

注册:创建main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider 路径的文件, 并在文件里配置我们创建的 Processor: com.ccino.ksp.ExtractInterfaceProcessorProvider

  1. 在app项目中使用我们创建的 ksp
bash 复制代码
plugins {
    //ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
    alias(libs.plugins.ksp)
}


dependencies {
    implementation(project(":kspDemo")) // 为了能够引入注解
    "ksp"(project(":kspDemo")) // 为了生成代码
}

大概步骤就是上面这样,具体还是直接看代码比较直观:github.com/ccnio/Wareh...

给 processor 传参

less 复制代码
ksp {
    arg("parameter", "paramValue")
}

// processor 里获取
environment.options["parameter"]

ksp 依赖关系和增量处理

增量编译用来提升编译速度,再次编译时尽量只处理有更改的文件,默认是开启的。

当 processor 启动后, resolver 法 getSymbolsWithAnnotation、getAllFiles 将仅返回脏文件和文件中的元素:

  • Resolver::getAllFiles - 仅返回脏文件引用。
  • Resolver::getSymbolsWithAnnotation - 仅返回脏文件中的符号。

对于每个输入文件,一般生成一个或多个输出文件。 当输入文件发生更改时,它就会变脏。 当为其生成相应的输出文件后,输入文件就变得干净了。还有一种情况,生成的文件不仅基于带注释的元素,还基于其父元素。 因此,如果此父级发生更改,则应重新处理该文件。为了确定哪些源需要重新处理,KSP 需要 processor 的帮助来识别哪些输入源对应于哪些输出源。 更具体地说,每当生成新文件时,我们都必须使用 Dependency 指定依赖项。Dependency 允许设置聚合参数,并且可以指定任意数量的文件依赖项。

场景一,指定生成的文件仅依赖于使用了带注解类的文件:

ini 复制代码
val dependencies = Dependencies(
    aggregating = false,
    annotatedClass.containingFile!!
)
val file = codeGenerator.createNewFile(
    dependencies,
    packageName,
    fileName
)

场景2,我们想让这个文件也依赖于其他文件,如包含带注释的类的父级的文件,我们需要显式地定义它们:

scss 复制代码
fun classWithParents(
    classDeclaration: KSClassDeclaration
): List<KSClassDeclaration> =
    classDeclaration.superTypes
        .map { it.resolve().declaration }
        .filterIsInstance<KSClassDeclaration>()
        .flatMap { classWithParents(it) }
        .toList()
        .plus(classDeclaration)

val aggregating = annotateClass.superTypes.first().toString() != "Any"
val dependencies = Dependencies(
    aggregating = aggregating,
    *classWithParents(annotatedClass)
        .mapNotNull { it.containingFile }
        .toTypedArray()
)

在大多数情况下,我们使用场景一,将聚合设置为 false(将此文件设置为隔离),并且我们依赖于用于生成此输出文件的文件,该文件通常是包含此带注释的元素的文件。(然而实际测试过程中,场景一的情况,修改了父类还是会造成对应文件的修改,不知道是api版本问题,还是我理解错误)。

如果我们的文件生成基于其他文件,我们还应该将它们列为依赖项(场景二)。 依赖于多个其他文件时应设置为聚合,但请记住,当任何文件更改时,聚合文件的依赖关系会变脏,这些变动也是会相互传递的。

ksp 日志无法打印问题

默认情况下 logger.info 是不打印的, logger.warn 及以上级别可以打印。想要打印 info 可以这样配置:

ksp 调试

参考 github.com/google/ksp/...

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui