上篇说到 Android 在编译过程中发起 kotlin 编译的流程。今天分享一下关于 Kotlin 编译里面比较重要的相关步骤:kapt。在 kotlin 里,需要使用 apt 的话,需要使用 kapt 插件来替代 annotationProcessor
的声明:
apply plugin: 'kotlin-kapt'
// 依赖
kat 'xx.xx:xxx'
在编译过程中编译任务会变成下面这样:
kapt 分成两部
生成 stubs 文件, 因为 apt 无法识别 Kotlin 语法,所以 kapt 第一步需要把 Kotlin 文件转换成 apt 可以识别出来的 Java 文件。
kapt 需要把 kotlin 文件生成一个 Java 编译可以认识的产物。但是直接翻译成 Java 文件是没有必要的,这里会生成一个和 kotlin 类类名、字段、方法签名一样但是没有具体实现的 Java 文件。Java文件存在module/build/tmp/kapt3/stubs/这个目录里面。
例如我们写一个 Kotlin 类:
class AKtDog {
private val TAG = "AKtDog"
fun playWith(age: Int): Int {
println(age)
println("2222")
println("1111")
return 11
}
}
kapt会生成这个 Kotlin 类的 stub 文件:
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
编译一样:
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)
)
}
通过上面的分析,我们可以发现,其实 apt 本身支持了增量编译,编译速度还是非常快的。
但是kapt生成stubs文件的流程在增量编译的时候就不是那么可控了,和 kotlin 编译一样,会有各种case让增量失效。那么全量重新生成一遍 stubs 文件那编译速度就有点慢了。
不过转念一想,我们是不是可以增量编译的时候在没有 apt 相关注解变动的时候直接禁用 generateStubs 这个 task呢?理论上这么做是可以的,所以我进行了一下尝试,在 gradle plugin 里面去寻找 generateStubs 的任务,然后禁用这个任务。最后我这里试验是成功的。具体做法如下:
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 中处理,来提升我们的编译效率。