Compose 1.12 强制 compileSdk 37 + AGP 9:牛马工程师的一站式升级避坑实录
背景与热点吐槽
7 月 28 日,Compose Multiplatform 1.12.0-beta03 发布。从 6 月 30 日 beta01 到现在,一个月连发三个 beta,JetBrains 这个节奏明摆着告诉你:稳定版不远了,赶紧准备。
但真正让我血压升高的不是 beta 本身,而是官方那句预告:Compose 1.12 将强制要求 compileSdk 37 和 Android Gradle Plugin 9。
翻译成人话就是:你想用 Compose 1.12?可以,先把 Android 17(API 37)的编译适配做了,再把 AGP 从 8.x 升到 9,AGP 9 又会拽着 Gradle 大版本一起动,Gradle 一动 Kotlin 版本兼容矩阵就得重新对表——而 Kotlin 2.4.0 恰好 6 月刚发稳定版。四个大版本手拉手一起来,这不是升级,这是渡劫。
我知道有同学会说"锁死版本不升不就完了"。兄弟,我们组去年就是这么想的,结果今年上半年一个三方库的安全补丁只在新版本发布,被迫一次性跨了三个 AGP 大版本,那两周我头发掉得比需求变更还快。技术债这东西,利息是复利。
技术原理拆解
先把这条依赖链捋清楚,升级顺序错了会白白多踩坑:
1. compileSdk 37 是入口门槛。 Android 17 延续了 trunk-stable 节奏在 6 月转正,Compose 1.12 的 UI 层要调用 API 37 的新平台能力(大屏自适应相关的窗口 API 收紧就是典型),所以 compileSdk 必须先到位。注意 compileSdk 升级本身风险很低——它只影响编译期可见的 API 面,真正有行为变更风险的是 targetSdk,这两件事可以拆开做。
2. AGP 9 是构建层的硬约束。 Compose 编译器插件和 AGP 之间有 Artifact 变换和字节码处理的深度耦合,1.12 用了 AGP 9 才有的构建管线能力。AGP 9 对应的 Gradle 最低版本也会抬升,如果你项目里还有手写的 Variant API 旧用法或者依赖了魔改 Transform 的老插件,这里是最大的雷区。先跑 ./gradlew :app:dependencies 和 AGP Upgrade Assistant 摸底,别裸奔。
3. Kotlin 2.4.0 需要重新对齐兼容矩阵。 K2 编译器已经是既定事实,2.4 主要是语言特性和编译性能的增量。但要盯死三个联动项:Compose Compiler(2.0 之后已随 Kotlin 版本走,这点反而省心了)、KSP 版本(必须严格匹配 Kotlin 版本号)、以及各注解处理器(Room、Hilt、Moshi)是否发了配套版本。
4. 测试 API v2 是隐形炸弹。 Compose 1.11 已经把测试 API v2 设为默认,v2 改了协程测试调度器的推进行为——以前靠 waitForIdle 侥幸通过的测试,现在时序变严格后会把既有代码里真实存在的竞态问题暴露出来。我们项目升 1.11 时挂了十几个 UI 测试,排查完发现全是测试写法在赌时序,怪不了框架。
代码示例或实操演示
Version Catalog 是这种多版本联动升级的救命稻草,先把目标版本集中声明:
# gradle/libs.versions.toml
[versions]
agp = "9.0.0"
kotlin = "2.4.0"
ksp = "2.4.0-1.0.x" # 严格跟 Kotlin 版本走
composeMultiplatform = "1.12.0-beta03"
compileSdk = "37"
targetSdk = "36" # target 先不动,拆开降险
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
模块配置里把 SDK 版本也从 Catalog 读,避免二十个模块各写各的:
// app/build.gradle.kts
android {
compileSdk = libs.versions.compileSdk.get().toInt()
defaultConfig {
targetSdk = libs.versions.targetSdk.get().toInt()
}
}
测试 API v2 下典型的竞态修复,核心思路是别赌空闲、要等条件:
// 旧写法:赌 waitForIdle 之后数据一定回来了
composeTestRule.waitForIdle()
composeTestRule.onNodeWithText("加载完成").assertExists()
// v2 下的正确姿势:显式等待条件成立
composeTestRule.waitUntil(timeoutMillis = 5_000) {
composeTestRule.onAllNodesWithText("加载完成")
.fetchSemanticsNodes().isNotEmpty()
}
牛马实践建议
结合我这几年被大版本升级反复毒打的经验,给几条真心话:
第一,现在就开分支摸底,但别急着合主干。 beta03 不是让你上生产的,是让你把升级成本摸清楚的。开一个 upgrade 分支,把 AGP 9 + Kotlin 2.4 + compileSdk 37 全量跑一遍编译和测试,把报错清单整理出来——等 1.12 稳定版发布那天,你是从容执行计划,而不是现场救火。
第二,升级顺序:Gradle/AGP → Kotlin/KSP → compileSdk → Compose,一步一提交。 每步保证能编译、能跑测试再走下一步。千万别一把梭全改完再排查,四个变量叠加的报错会让你怀疑人生。
第三,compileSdk 和 targetSdk 拆开。 compileSdk 37 是给 Compose 1.12 让路的,先升;targetSdk 涉及运行时行为变更,走独立的适配排期,拿 Android 17 行为变更文档逐条过。
第四,把三方依赖健康度纳入决策。 跑一遍依赖树,看哪些库半年没更新还深度依赖旧 AGP API。这种库这次不换,下次升级还会咬你。从组件架构角度说,这正是给这类依赖加防腐层(接口隔离 + 单独模块封装)的好时机,下次再换就是改一个模块的事。
第五,性能基线先留档。 Kotlin 2.4 和 AGP 9 都宣传构建提速,但"宣传"和"你的项目"是两回事。升级前用 gradle-profiler 或者最土的 --profile 把 clean build / 增量 build 时间记下来,升级后对比,有数据才能跟老板汇报"这次升级不白干"。
总结与展望
这波 Compose 1.12 + AGP 9 + Kotlin 2.4 + SDK 37 的组合升级,本质上是 Android 工具链一次集中的"底座换代":K2 编译器全面接管、AGP 构建管线现代化、平台 API 向大屏自适应收紧。恰逢 Compose 五周年,官方在 1.11/1.12 里铺的实验性 MediaQuery、Grid、Styles API 也能看出方向——声明式 UI 正在向"响应式布局原生化"演进。
吐槽归吐槽,说句公道话:相比五年前 Support 库时代一次升级崩半个项目的日子,现在有 Version Catalog、有兼容矩阵文档、Compose Compiler 还跟 Kotlin 版本合并了,升级体验其实是在变好的。卷不动是真的,但这次的劫,早渡早超生。预留两周排期,按顺序拆步骤执行,别等稳定版发布叠加业务需求一起爆炸——这是我作为一个渡过 N 次劫的老牛马,能给你的最实在的建议。
常见问题 FAQ
Q1:现在就要升级到 Compose 1.12 beta 吗?
A:生产环境不建议。beta 阶段的正确用法是开独立分支做升级摸底:跑通编译、整理三方库兼容清单、评估测试改造量。等稳定版发布后按已验证的清单执行,把风险前置消化。
Q2:compileSdk 37 和 targetSdk 37 必须一起升吗?
A:不必须,强烈建议拆开。compileSdk 只影响编译期 API 可见性,风险很低,为满足 Compose 1.12 要求先升即可;targetSdk 才会触发 Android 17 的运行时行为变更,应单独排期、逐条对照官方行为变更文档做适配和回归。
Q3:升级 AGP 9 最容易踩的坑是什么?
A:两类:一是项目里手写的旧 Variant API 用法和依赖旧 Transform 机制的第三方 Gradle 插件,在 AGP 9 下可能直接失效;二是 Gradle 版本联动抬升后,自定义构建逻辑(buildSrc、convention plugin)需要同步适配。建议先用 AGP Upgrade Assistant 和依赖树摸底再动手。
Q4:升级后大量 Compose UI 测试失败怎么办?
A:大概率是测试 API v2 的协程调度器行为变严格,暴露了原有测试对时序的隐式依赖。修复方向是把赌时序的 waitForIdle 断言改成 waitUntil 显式等待条件成立,本质上是修正测试写法,而不是框架的锅。
Q5:Kotlin 2.4 升级还需要单独管理 Compose Compiler 版本吗?
A:不需要。自 Kotlin 2.0 起 Compose Compiler 已并入 Kotlin 仓库随版本发布,通过 org.jetbrains.kotlin.plugin.compose 插件引用即可。真正要严格对表的是 KSP 版本(必须匹配 Kotlin 版本号)以及 Room、Hilt 等注解处理器的配套版本。