你错过的 18 个月
这一章是一份清单,不是一篇散文。我把 2024 年中到 2026 年中之间的变化筛过一遍,扔掉了所有「知道了也不影响你写代码」的东西,只留下六条真的会改变你手指怎么敲的。每条都标了它属于哪一类:该立刻上(不上就是在给自己挖坑)、该知道(不一定用得上,但别人聊起来你得懂)、可以先不管(热闹,但不急)。
一 · Compose 编译器搬进了 Kotlin
该立刻上 Kotlin 2.0 起
你还记得那张版本对照表吗?Compose 编译器扩展版本必须和 Kotlin 版本严格配对,升一个 Kotlin 小版本 要先去查表确认编译器有没有跟上 —— 没跟上就得等,一等好几周。
那张表没了。Compose 编译器从 Kotlin 2.0 起并入了 Kotlin 仓库,跟着 Kotlin 一起发版。
// 以前(现在会报错):
android {
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14" // ← 得去查表
}
}
// 现在:它就是个普通的 Kotlin 编译器插件
plugins {
alias(libs.plugins.kotlin.compose) // org.jetbrains.kotlin.plugin.compose
}
android {
buildFeatures { compose = true }
}
真正的意义是:Compose 不再是个「挂在 Kotlin 外面的第三方东西」,它是 Kotlin 工具链的一部分。 Kotlin 出新版本,Compose 当天就能用。这也是后面所有事情(多平台、编译器插件式 DI)的前提。
二 · 强跳过默认开启 —— 你可以删掉那些 @Immutable 了
该立刻上 Kotlin 2.0.20+ 默认
这条是本章最实惠的一条。先回忆一下你离开前的痛:Compose 的「稳定性」。
当年的规则很硬:一个 composable 想被跳过(参数没变就不重画),它的所有参数都得是稳定的。
而 List、Set、Map 是接口 —— 编译器无法保证它们不会被偷偷改掉 —— 所以统统判定为不稳定。
结果:一个参数里带 List 的 composable,整个函数「不可跳过」,父一重组它必重组,连带整棵子树跟着重画。
于是我们发明了一堆体操:给 data class 打 @Immutable、引入 kotlinx-collections-immutable 的
ImmutableList、把 lambda 用 remember 包起来……
强跳过(strong skipping)把这一整套体操废掉了。它默认开启,你什么都不用做。它干了两件事:
- 不稳定参数改用「实例比较」。以前不稳定 = 不可跳过;现在是:比一比这个
List还是不是同一个对象(===)。是,就跳过。 - lambda 自动记忆化。你写在参数里的
{ ... },编译器自动帮你remember了,不会每次重组都造一个新对象。
下面这个可视化,拿同一棵 composable 树、同一个改动,在三种规则下跑一遍,把重组的节点数数给你看。 先看默认的「改 title」:老规则重组 6 个节点,新规则只重组 2 个。
- 绝大多数
@Immutable/@Stable注解 —— 加了也不再有额外收益。 - 为了稳定性而引入的
ImmutableList/PersistentList—— 普通List现在够用了。 (如果你是为了真的不可变语义而用它,那另说,继续用。) - 手工把 lambda
remember { }起来的那些代码。
删之前跑一遍 Compose 编译器的指标报告确认一下,但大概率你会删掉一大片。
在上面的 Demo 里切到「收藏第 2 篇」,你会看到:强跳过模式下,三张卡片全部重组了 —— 可只有第 2 张的内容真的变了。重组数和老规则一样是 5。
凶手是那个 lambda。{ id -> vm.toggle(id, id in bookmarks) } 捕获了 bookmarks。
编译器确实帮你记忆化了它,但 bookmarks 一变,这个记忆化的 lambda 就得重建 ——
于是每张卡片都看到「我的 onBookmark 参数变了」,全部重组。
修法在 Demo 的第三个选项里:用方法引用 vm::toggle,不捕获任何东西。
重组数立刻从 5 掉到 3(只有真正变了的那张卡重组)。
规律:强跳过消灭了「类型不稳定」这个问题,但没有消灭「lambda 捕获」这个问题。 列表项的回调,尽量别在里面捕获会变的东西。
三 · 导航:一次跳了两级
该立刻上 Nav 2.8(2024-08) → Nav3(2025-11 转正)
你离开的时候,导航还是这样写的:
// 2024 年上半年:字符串路由
navController.navigate("detail/${article.id}")
composable("detail/{id}") { backStackEntry ->
val id = backStackEntry.arguments?.getString("id") // 手动挖参数,类型全靠祈祷
DetailScreen(id!!)
}
然后发生了两件事,而且是接连发生的:
如果你的项目还在用字符串路由,不必先迁到 Nav 2.8 的类型安全路由、再迁到 Nav3。 Nav3 是完全不同的一套 API,中间那一级对你没有任何过渡价值。直接看第 3 章,一步到位。
四 · 生命周期组件全面多平台化
该知道 2024–2025 陆续
这条你不一定用得上,但它是理解「为什么社区在往那个方向走」的钥匙。这一年半里, Jetpack 把一批核心库搬进了 Kotlin Multiplatform:
| 库 | 现在的样子 | 意味着什么 |
|---|---|---|
lifecycle-viewmodel |
多平台 | commonMain 里可以放一个真的 ViewModel,连 viewModelScope 和 SavedStateHandle 都在 |
Room |
多平台 | 同一份 DAO 和 @Entity,Android / iOS / Desktop 通吃 |
DataStore |
多平台 | 偏好存储也下沉了 |
Coil 3 |
多平台 | 图片加载 —— Mihon 已经在用 3.5 |
navigation3-runtime |
多平台 | 连导航都能共享了 |
真正的后果是:第 1 章那三层,现在可以整体住进 commonMain。数据层、状态持有者、
甚至 UI 层(Compose Multiplatform),都能一次写完两端用。
Compose 的 iOS 支持自 1.8.0(2025 年 5 月)起标记为稳定,如今已到 1.10.x。 但要泼一盆冷水:iOS 的无障碍(accessibility)支持至今仍是公认的短板。 做医疗、金融、政务这类有合规要求的 app,上 CMP 之前务必先把这条摸清楚。
五 · Material 3 Expressive
可以先不管 2025 年 I/O 发布,组件陆续转正
新的一套设计语言:更大胆的形状、更有个性的动效,以及一批新组件(FloatingToolbar、FAB 菜单、
新的加载指示器等)。它们正在从实验性 API 逐步转正。
为什么归到「可以先不管」:它是视觉层的事,不影响架构。你的分层、状态流、导航一行都不用改。 等设计师找上门时再学不迟。但别把它和 Material 3 搞混 —— M3 本身早就是标配了。
六 · 平台侧:AGP 9、targetSdk 36 和两条硬性要求
该立刻上 Android 16
这两条是平台强制的,和你想不想升级无关 —— Play 商店的 targetSdk 门槛会推着你走:
-
edge-to-edge 全面强制。Android 15 时还留了个临时的 opt-out 开关,到 Android 16 那个开关
不再被尊重。你的内容一定会画到状态栏和导航栏底下。
所以
WindowInsets不再是「适配刘海屏」的可选功课,而是必修课 —— 该加Modifier.safeDrawingPadding()的地方一个都不能少,否则按钮就被系统手势条盖住了。 -
大屏不能再锁方向。在大屏设备上(可用宽度 ≥ 600dp),你在 manifest 里写的
screenOrientation和resizableActivity会被忽略。 「我们是手机 app,锁死竖屏就行」这个偷懒选项,在平板和折叠屏上没有了。 这直接把「自适应布局」从加分项变成了必答题 —— 这也正是 Navigation 3 把 多窗格做进核心 API 的原因(第 3 章)。
构建侧顺带一提:Now in Android 现在跑在 AGP 9.0 + Kotlin 2.3 上, Mihon 更激进,已经上了 Kotlin 2.4。K2 编译器早就是默认了。
清单总表
| 变化 | 紧急度 | 你要做的事 |
|---|---|---|
| Compose 编译器并入 Kotlin | 立刻 | 删掉 composeOptions,换成 kotlin.plugin.compose |
| 强跳过默认开启 | 立刻 | 删掉大部分 @Immutable;但检查 lambda 有没有捕获会变的值 |
| Navigation 3 | 立刻 | 新项目直接上;老项目评估迁移(下一章)。别在 Nav2 类型安全路由上停留 |
| edge-to-edge / 大屏不锁方向 | 立刻 | 平台强制,躲不掉。补 WindowInsets,补自适应布局 |
| 核心库多平台化 | 该知道 | 不做 KMP 也要知道 —— 它解释了社区为什么在往那个方向长 |
| Material 3 Expressive | 可以先不管 | 视觉层,不影响架构 |
1. 在你的项目里全局搜 @Immutable 和 ImmutableList,数一数有多少个 —— 那就是你曾经付出的稳定性税。
2. 搜一遍 LazyColumn 里的 onClick lambda,看看它们捕获了什么。捕获了会变的值?那你就中了上面那个坑。
清单过完了。其中五条是「知道了就能立刻动手」的,但有一条不行 —— Navigation 3 值得单开一章。 因为它不是一次 API 升级,而是把「返回栈归谁管」这个问题的答案彻底掉了个个儿。下一章。