KSP准备好大规模应用了吗? - 1

361 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 22 天,点击查看活动详情

通过与与KAPT比较, 简要介绍KSP(Kotlin符号处理). KSP准备好大规模应用了吗?

要使你的代码更容易并跳过大量的模板代码, 代码生成是最有用的功能之一. 花在编译上的时间增加了, 但你的代码更紧凑, 更容易支持. Kotlin源文件不能直接用于注释处理, 所以在KAPT中实现了某种变通方法来跳过这个限制. 这种变通方法也会增加编译时间, KSP(Kotlin符号处理)是由谷歌开发的, 用于解决这些问题. 它很厉害, 但还没有准备好让开发者大规模采用, 我在最后解释了原因.

代码生成的含义

主要目标是一个或多个文件被转换为代码.

R.class就是一个使用这种机制的好例子. Android Gradle插件解析XML文件(strings.xml, values.xml等), 以便得到包含XML文件中所有键的R.class. 该插件对如何组合这些文件有一定的规则, 例如, 来自strings.xml的键转换为R.strings.{key}.

在3.6版本之前, 该插件已经生成了R.java源代码. 从3.6开始, 该插件生成了一个字节码(加快了编译时间).

对于源文件, 同样的技巧能够做到. 它被称为注解处理器, 注解被用作标记, 在处理过程中可以使用.

首先, 注解是什么意思?

在注释处理API(JSR-175, 2006)之前, 注释已经被引入Java(JSR-175, 2004). 它们能够将Java类型的元数据保留到Java源文件中.

有三个目标:

  • SOURCE 只在编译阶段可用, Java编译器将它们从编译的JVM(Java虚拟机)字节码中删除.
  • CLASS 被存储到JVM字节码中, 以便在将字节加载到VM(虚拟机)之前对字节码进行操作, 但它在Runtime中不可用.
  • RUNTIME 通过反射在Runtime中可用, 它能够在不重新编译的情况下扩展功能, 并在飞行中完成.

每个Kotlin类都有kotlin.Metadata注解. 该注解有RUNTIME目标. Kotlin编译器在编译时使用它们, 并在Runtime中使它们可用.

再来看看注解处理器. 注释处理器是可插拔的注释处理API(JSR-269)的实现. 其主要思想与XML文件相同, 其中注释是用来代替键的. 向你展示空的注释处理器代码.

class TestProcessor : AbstractProcessor() {
  
  // Denotes maximum supported Java version
  override fun getSupportedSourceVersion(): SourceVersion {
    return SourceVersion.latestSupported()
  }
  
  // Denotes annotations that will be processed
  override fun getSupportedAnnotationTypes(): MutableSet<String> {
    return mutableSetOf(Builder::class.java.name)
  }

  // Called by Java compiler to process current round
  override fun process(
    // Contains found annotation in current round
    annotations: MutableSet<out TypeElement>?,
    // Gives access to Java compiler details 
    // and contains a result from previous round
    roundEnv: RoundEnvironment?
  ): Boolean { /* Perform annotation processing here */ }

}

首先, Java编译器分析所有的源文件和所有的依赖关系并建立AST(抽象语法树). 试着想象一下, 你写的所有代码看起来就像一棵树, 对编译器来说有很多分支. 树上的每个元素都被称为TypeElement.

注释处理器是编译器的一个插件, 所以当编译器调用处理器时, 会使用相同的AST.

一个注释处理器有以下方法:

  • getSupportedSourceVersion表示最大支持哪种Java语言版本.
  • getSupportedAnnotationTypes返回要处理的注解.
  • process返回一个圆形的处理结果.

另外, process有2个参数:

  • annotations - 此轮的注释被发现.
  • roundEnv - 这是支持的实体, 可以访问编译器数据并获得前一轮的结果.

一轮的含义与一个周期中的迭代相同, 在这个周期中, 编译器会多次迭代代码以解决所有的依赖关系.

为了处理注解, 处理器会执行几轮处理, 为什么?

  • 一个处理器可以生成新的类型, 里面有注解, 它们也会被处理.
  • 类型元素可以是无效的, 例如依赖新生成的类型, 在无效的时候跳过几轮.

与此同时, Kotlin开始在Android开发中越来越受欢迎, Google正式将Kotlin作为Android的支持语言. Kotlin有兼容的JVM字节码. 这意味着Java和Kotlin的编译器将源文件编译成单一的文件格式, 对虚拟机来说是可以理解的.

没有注释处理器, Kotlin就不能被大规模采用. 而且我们不能使用相同的注释处理器, Kotlin和Java有不同的语法, 但处理器是用Java AST工作的, 不理解Kotlin AST. JetBrains发布的KAPT已经解决了这个限制.

KAPT使用了某种变通方法, 它允许对Kotlin源文件重新使用已经写好的注释处理器.

敬请期待KAPT和KSP的内容...

KSP准备好大规模应用了吗? - 2

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 22 天,点击查看活动详情