正题
上篇说到 Android 在编译过程中发起 kotlin 编译的流程。今天分享一下关于 Kotlin 编译里面比较重要的相关步骤:kapt。在 kotlin 里,需要使用 apt 的话,需要使用 kapt 插件来替代 annotationProcessor
的声明:
apply plugin: 'kotlin-kapt'
// 依赖
kat 'xx.xx:xxx'
在编译过程中编译任务会变成下面这样:
kapt 分成两部
generateStubs
生成 stubs 文件, 因为 apt 无法识别 Kotlin 语法,所以 kapt 第一步需要把 Kotlin 文件转换成 apt 可以识别出来的 Java 文件。
kapt 需要把 kotlin 文件生成一个 Java 编译可以认识的产物。但是直接翻译成 Java 文件是没有必要的,这里会生成一个和 kotlin 类类名、字段、方法签名一样但是没有具体实现的 Java 文件。Java文件存在module/build/tmp/kapt3/stubs/这个目录里面。
例如我们写一个 Kotlin 类:
代码语言:javascript复制class AKtDog {
private val TAG = "AKtDog"
fun playWith(age: Int): Int {
println(age)
println("2222")
println("1111")
return 11
}
}
kapt会生成这个 Kotlin 类的 stub 文件:
代码语言:javascript复制import java.lang.System;
@kotlin.Metadata
public final class AKtDog {
private final java.lang.String TAG = "AKtDog";
public AKtDog() {
super();
}
public final int playWith(int age) {
return 0;
}
}
这里生成的 Java 文件的 playWith
方法里其实是没有逻辑的,所以只是一个壳,把类的 abi 提供给 apt。
这个 task 的源码可以在 kotlin
的源码里面找到: org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask
。
这个 Task
继承自 org.jetbrains.kotlin.gradle.tasks.KotlinCompiler
, 说明生成 stubs 的流程其实是遵循了 Kotlin
编译的过程的。
这里调用 Kotlin
编译的时候会带入 kapt 相关的命令行参数:-Xplugin=xx/xx/xx/kotlin-annotation-processing-gradle-x.x.x-sources.jar
generateStubs 也支持增量编译,相关的编译缓存内容也和 Koltin
编译一样:
kapt build
kotlin apt 编译,执行 apt 流程生成代码。
那么 apt 是咋触发的呢,这个也可以在 Kotlin
的源码里面找到。这部分的 task 源码在 org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask
里面
在它的 @TaskAction
里面会调用 kapt 的任务:org.jetbrains.kotlin.gradle.internal.KaptExecution
:
// 定义 kapt class
private fun kaptClass(classLoader: ClassLoader) = Class.forName("org.jetbrains.kotlin.kapt3.base.Kapt", true, classLoader)
val kaptMethod = kaptClass(kaptClassLoader).declaredMethods.single { it.name == "kapt" }
kaptMethod.invoke(null, createKaptOptions(kaptClassLoader))
这里执行的是 org.jetbrains.kotlin.kapt3.base.Kapt
对象的 kapt
方法。这个里面就会和 Java 编译一样去调用我们的 apt 了:
val annotationProcessingTime = measureTimeMillis {
kaptContext.doAnnotationProcessing(
javaSourceFiles,
processors.processors,
binaryTypesToReprocess = collectAggregatedTypes(kaptContext.sourcesToReprocess)
)
}
禁用kapt
通过上面的分析,我们可以发现,其实 apt 本身支持了增量编译,编译速度还是非常快的。
但是kapt生成stubs文件的流程在增量编译的时候就不是那么可控了,和 kotlin 编译一样,会有各种case让增量失效。那么全量重新生成一遍 stubs 文件那编译速度就有点慢了。
不过转念一想,我们是不是可以增量编译的时候在没有 apt 相关注解变动的时候直接禁用 generateStubs 这个 task呢?理论上这么做是可以的,所以我进行了一下尝试,在 gradle plugin 里面去寻找 generateStubs 的任务,然后禁用这个任务。最后我这里试验是成功的。具体做法如下:
代码语言:javascript复制project.tasks.whenTaskAdded {
if (it.name.contains("kaptGenerateStubs")) {
it.enabled = false
}
}
这里我们把 KaptGenerateStubs
相关的 task 的 enabled
设置为 false,最后编译的时候,这个task则会跳过,控制台的输出如下:
> Task :amodule:kaptGenerateStubsDebugKotlin SKIPPED
总结
今天分享了一下 kapt 相关的内容,我们可以从中相关的内容了解 kapt 的原理。在未来我们可以及时拥抱 ksp 等新技术,直接在 kotlin 的 AST 中处理,来提升我们的编译效率。