跳到主要内容

为什么选择 KSP

编译器插件是强大的元编程工具,可以极大地增强你编写代码的方式。 编译器插件直接将编译器作为库调用,以分析和编辑输入程序。这些插件还可以生成用于各种用途的输出。例如,它们可以生成样板代码,甚至可以为特殊标记的程序元素(例如 Parcelable)生成完整的实现。插件具有多种其他用途,甚至可以用于实现和微调语言中未直接提供的功能。

虽然编译器插件功能强大,但这种强大功能是有代价的。要编写即使是最简单的插件,你也需要具备一些编译器背景知识,并且需要对特定编译器的实现细节有一定的了解。另一个实际问题是,插件通常与特定的编译器版本紧密相关,这意味着每次要支持较新版本的编译器时,你可能都需要更新插件。

KSP 使创建轻量级编译器插件更容易

KSP 旨在隐藏编译器更改,从而最大限度地减少使用它的处理器的维护工作。KSP 的设计目的并非与 JVM 绑定,以便将来可以更轻松地适应其他平台。KSP 还旨在最大限度地缩短构建时间。对于某些处理器,例如 Glide,与 kapt 相比,KSP 最多可将完整编译时间缩短 25%。

KSP 本身是作为编译器插件实现的。Google 的 Maven 仓库上有预构建的软件包,你可以下载和使用,而无需自己构建项目。

kotlinc 编译器插件的比较

kotlinc 编译器插件几乎可以访问编译器的所有内容,因此具有最大的 power 和灵活性。 另一方面,由于这些插件可能会依赖于编译器中的任何内容,因此它们对编译器更改很敏感,需要经常维护。这些插件还需要深入了解 kotlinc 的实现,因此学习曲线可能很陡峭。

KSP 旨在通过定义良好的 API 隐藏大多数编译器更改,尽管编译器甚至 Kotlin 语言的重大更改可能仍需要向 API 用户公开。

KSP 尝试通过提供一种以 power 换取简单性的 API 来满足常见的用例。它的功能是通用 kotlinc 插件的严格子集。例如,虽然 kotlinc 可以检查表达式和语句,甚至可以修改代码,但 KSP 不能。

虽然编写 kotlinc 插件可能非常有趣,但也可能花费大量时间。如果你无法学习 kotlinc 的实现并且不需要修改源代码或读取表达式,那么 KSP 可能是个不错的选择。

与反射的比较

KSP 的 API 看起来类似于 kotlin.reflect。它们之间的主要区别在于,KSP 中的类型引用需要显式解析。这是未共享接口的原因之一。

与 kapt 的比较

kapt 是一个卓越的解决方案,它使大量 Java 注解处理器可以直接用于 Kotlin 程序。与 kapt 相比,KSP 的主要优势在于提高了构建性能,不与 JVM 绑定,具有更符合语言习惯的 Kotlin API,并且能够理解仅 Kotlin 的符号。

为了未经修改地运行 Java 注解处理器,kapt 将 Kotlin 代码编译为 Java 桩,这些桩保留了 Java 注解处理器关心的信息。要创建这些桩,kapt 需要解析 Kotlin 程序中的所有符号。桩生成花费大约 1/3 的完整 kotlinc 分析和相同顺序的 kotlinc 代码生成。对于许多注解处理器,这比在处理器本身中花费的时间要长得多。例如,Glide 查看数量非常有限的带有预定义注解的类,并且其代码生成速度相当快。几乎所有的构建开销都存在于桩生成阶段。切换到 KSP 将立即减少编译器中花费的时间 25%。

为了进行性能评估,我们用 KSP 实现了 Glide 的一个 简化版本,以使其为 Tachiyomi 项目生成代码。虽然该项目的 Kotlin 总编译时间在我们的测试设备上为 21.55 秒,但 kapt 花费了 8.67 秒来生成代码,而我们的 KSP 实现花费了 1.15 秒来生成代码。

与 kapt 不同,KSP 中的处理器不会从 Java 的角度查看输入程序。该 API 对于 Kotlin 来说更为自然,尤其是对于 Kotlin 特有的功能(如顶层函数)。由于 KSP 不像 kapt 那样委托给 javac,因此它不假定特定于 JVM 的行为,并且可以与潜在的其他平台一起使用。

局限性

虽然 KSP 试图成为大多数常见用例的简单解决方案,但与其他插件解决方案相比,它做出了一些权衡。 以下不是 KSP 的目标:

  • 检查源代码的表达式级别信息。
  • 修改源代码。
  • 100% 兼容 Java 注解处理 API。