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/...

相关推荐
androidwork6 小时前
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
android·java·kotlin·androidx
每次的天空6 小时前
Android第十三次面试总结基础
android·面试·职场和发展
wu_android6 小时前
Android 相对布局管理器(RelativeLayout)
android
李斯维8 小时前
循序渐进 Android Binder(二):传递自定义对象和 AIDL 回调
android·java·android studio
androidwork8 小时前
OkHttp 3.0源码解析:从设计理念到核心实现
android·java·okhttp·kotlin
像风一样自由9 小时前
【001】frida API分类 总览
android·frida
casual_clover9 小时前
Android 之 kotlin 语言学习笔记四(Android KTX)
android·学习·kotlin
移动开发者1号11 小时前
Android 大文件分块上传实战:突破表单数据限制的完整方案
android·java·kotlin
移动开发者1号11 小时前
单线程模型中消息机制解析
android·kotlin
每次的天空13 小时前
Android第十五次面试总结(第三方组件和adb命令)
android