Administrator
发布于 2026-08-25 / 40 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 71 篇:Kotlin 2.0 与 K2 编译器:一次真正的"换芯"手术

前言

2024 年 Kotlin 团队把 K2 编译器标记为稳定版(Stable),2025 年正式默认开启。这个动作意味着——过去几年我们一直在调试的"老 K"前端,正式退役。换来的不只是编译快一点,而是整个类型系统、解析器的重写。

这一篇不扯官方公告里的漂亮话。我把日常开发会撞到的几个分水岭摆出来:哪些代码会立刻变快、哪些写法突然报错、Compose 编译器怎么处理。

一、K2 到底换了什么

K2 不只是给 Kotlin Compiler 加了一层缓存,它把 Frontend (前端解析) 彻底换成新的架构:

维度

老编译器(K1)

K2

架构

单遍解析 + PSI

FIR (Front-end IR) + 多遍

类型推导

容易在泛型递归里"懵"

FIR 一次成型,几乎不掉推导

增量编译

Gradle ABI 命中粒度粗

FIR 直接产出增量缓存切片

宏/插件

KAPT/ksp 都得绕过老前端

KSP 2 原生对接 FIR

编译速度

基线 1x

实测 1.5-2x(CI 比本地更明显)

最直观感受:第一次 build 还是慢,但只要有缓存,切分支后绝大部分工程能"秒编"。

二、几个会立刻影响你写法的点

2.1 智能转型(Smart Cast)更激进

K2 在赋值、if/else 分支、null 检查后的智能转型推断范围更广了:

fun handle(req: Request?) {
    // K2 下可以直接把 req 当成 Request 用
    if (req != null && req.method == "POST") {
        // req 在这里不再是 Request?
        println(req.body.length)
    }
}

K1 里上面这段偶尔会要求你 req?.let { ... } 或者 !!。K2 会更"信任"你——但反过来,类型不严谨的代码也可能跳过警告,被放过。所以开启 K2 之后,建议把 detekt / ktlint 的规则重新校准一遍。

2.2 伴生对象(Companion)的可见性检查变严

K1 时代能跑通的反射访问,K2 下直接抛 IllegalAccessError。例如:

class Holder {
    companion object {
        internal val secret = "..."
    }
}

// K2 下:编译期就会拒绝
Holdersecret

建议:在 K2 工程里不要再依赖 internal 反射。公开 API 用,私有 API 不要试图"聪明地绕过去"。

2.3 注解的处理顺序改变

KAPT 的痛,KSP 解决。但 K2 + KSP 还有个隐藏好处:注解处理可以在 FIR 里完成,意味着增量编译时修改注解不会触发全模块重编。

迁移实践:

  • kapt { correctErrorTypes = true } 这种老配置可以删

  • 改用 ksp("com.google.dagger:hilt-android-compiler:2.51")

  • moshi-kotlin-codegen 也走 KSP 路径

2.4 sealed class 的 when 分支对 enum data 校验更严

sealed class UiState {
    object Idle : UiState()
    data class Data(val value: Int) : UiState()
    data class Error(val code: Int, val msg: String) : UiState()
}

fun render(s: UiState) = when (s) {
    is UiState.Idle -> "..."
    is UiState.Data -> s.value.toString()
    // K2 下:如果漏掉 Error,编译期硬报错,不再是警告
}

三、Compose Compiler 的 K2 适配

JetBrains 在 Compose 1.5 之后官方推荐把 Compose Compiler 切到 K2 模式。需要做的几件事:

// gradle.properties
kotlin.experimental.tryK2=true
kotlin.code.style=official

// build.gradle.kts
composeCompiler {
    enableIntrinsicRemember = true
    enableNonSkippingGroupOptimization = true
}

迁移后的实际收益:

  • @Composable 函数的可重启粒度(restartable groups)更细,跳过不必要重组

  • 默认 Strong Skipping 模式下,lambda 捕获更稳定

  • derivedStateOf 的失效判断少一次重算

但也要小心:开发期间如果 IDE 报"Composable invocations can only happen from the context of a @Composable function",不要强 build,这种提示在 K2 下基本就是真的。

四、回退与诊断

如果你升级后某些老工程编不过,先别慌。两条应急通道:

  1. 局部回退单个模块:

// build.gradle.kts
kotlin {
    compilerOptions {
        languageVersion.set(KotlinVersion.KOTLIN_1_9)
    }
}
  1. 查看 K2 内部诊断:

./gradlew compileDebugKotlin --info 2>&1 | grep -i "k2\|fir" | head -50

K2 编译失败的时候,错误信息会比 K1 更具体——它会告诉你"哪一步 FIR 校验没过"。

五、避坑清单(来自真实迁移)

  • 升级 Kotlin 到 2.0 之前,把 KSP 全量更新到 2.x,老 KSP 版本会冲突

  • kotlinx-metadata-jvm 必须升级到 0.9+,否则反射注解读不到

  • 第三方注解处理器(Hilt、Room、Moshi)务必升级最新 stable

  • IDE 插件 Kotlin Plugin 与工程 Kotlin 版本要严格一致(差一个小版本都可能让 K2 mode 退化)

  • 单元测试套件覆盖率 ≥70% 的工程再上 K2,否则回退成本太高

小结

K2 不是一个"升级一下重启就好"的版本,它是一次前端范式的切换。日常写法的变化主要集中在:智能转型、可见性、注解处理、sealed 类型穷尽。建议小步推进:先用 K2 编新写的模块,老模块按升级路径慢慢切。

下一篇我们要聊 Gradle 8.x 与构建优化——一旦 Kotlin 编译快了,构建链路上其他瓶颈就会被放大,特别是 Configuration Cache 和 Build Cache 的搭配。

参考阅读

  • Kotlin 官方博客:K2 Compiler Performance Benchmarks

  • Android Developers 博客:Migrating to K2

  • Gradle 官方文档:Configuration Cache 章节

  • Compose Compiler 2.0 Release Notes


评论