一、升级概览
1.1 版本变更总览
| 组件 | 升级前 | 升级后 | 说明 |
|---|---|---|---|
| Android Gradle Plugin (AGP) | 8.6.0 | 9.2.1 | 主升级目标 |
| Gradle | 8.x | 9.4.1 | AGP 9 强制要求 |
| Kotlin | 2.1.0 | 2.3.0 | AGP 9 内置 Kotlin 要求对齐 |
| KSP | 2.1.0-1.0.29 | 2.3.6 | 跟随 Kotlin 2.3(新版本号方案) |
| Room | 2.5.2 | 2.8.4 | 修复 KSP2 兼容性 bug |
| compileSdk | 36 | 36 | 不变 |
| minSdk | 26 | 26 | 不变 |
| targetSdk | 36 | 36 | 不变 |
| JDK | 17 | 17 | 不变(AGP 9 最低要求 17) |
1.2 升级收益
- 构建性能:内置 Kotlin、非 final R class(本项目暂保留 final)、配置缓存优化
- 现代化 DSL:统一走 Variant API、移除大量 deprecated API
- R8 优化:默认开启更激进的代码优化与资源压缩
- 工具链对齐:与 Android Studio 2026.1.1(Meerkat)完美匹配
1.3 升级耗时
- 实际编码与调试:约 2 个工作日
- 涉及修改文件:30 个(含 2 个文件重命名)
- 解决各类问题:12 类(环境、版本、DSL、API、编译)
二、环境前置条件
2.1 必备环境
| 项 | 要求 |
|---|---|
| Android Studio | 2025.2(Otter)或更新,推荐 2026.1.1(Meerkat) |
| JDK | 17 或更高(AGP 9 最低要求 17) |
| Gradle | 9.1.0+(项目实际使用 9.4.1) |
| SDK Build Tools | 36.0.0 |
| NDK | 28.2.13676358(如用到) |
2.2 网络环境(关键)
AGP 9.2.1 全套依赖(约 30+ jar)需从 Google Maven(dl.google.com)下载,国内网络环境需配置代理。本项目在 gradle.properties 中配置了项目级代理:
systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=9098
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=9098
# 内网仓库不走代理
systemProp.http.nonProxyHosts=localhost|127.0.0.1|10.*|*.sohuno.com|*.sohurdc.com
systemProp.https.nonProxyHosts=localhost|127.0.0.1|10.*|*.sohuno.com|*.sohurdc.com
# 代理网络不稳时增加超时容忍
systemProp.org.gradle.internal.http.connectionTimeout=120000
systemProp.org.gradle.internal.http.socketTimeout=120000
注意:代理配置变更后必须 kill 所有 Gradle/Kotlin daemon,否则会沿用旧环境变量。
pkill -f GradleDaemon pkill -f KotlinCompileDaemon
2.3 代理配置清理(踩坑记录)
升级过程中遇到多次代理冲突,需要排查并清理以下三处:
| 位置 | 文件 | 处理方式 |
|---|---|---|
| 全局 Gradle | ~/.gradle/gradle.properties |
删除 systemProp.http.proxyHost 等行 |
| Shell 环境 | ~/.zshrc |
将 export ALL_PROXY=... 改为 proxy_on() / proxy_off() 函数 |
| IDE 配置 | ~/Library/Application Support/Google/AndroidStudio2026.1.1/options/proxy.settings.xml |
清空 PROXY_HOST / PROXY_PORT 字段 |
同时建议在 Android Studio 设置中关闭 Download Gradle Sources,减少下载量(在弱网下尤其重要)。
三、升级步骤
3.1 第一步:AGP Upgrade Assistant
- 打开 Android Studio → Tools → AGP Upgrade Assistant
- 在 "Target Android Gradle Plugin Version" 下拉框手动选择 9.x(默认显示 8.13,需手动切换)
- 执行升级,让助手自动处理基础版本号和部分 deprecated 标志
3.2 第二步:移除 AGP 9 默认行为的 opt-out 标志
gradle.properties 中以下标志在 AGP 9 已变为默认行为,需移除(或注释):
# 以下 3 项已被 AGP 9 作为默认值启用,原 opt-out 标志需移除
# android.builtInKotlin=false # 内置 Kotlin(已默认启用)
# android.newDsl=false # 新 DSL(已默认启用)
# android.uniquePackageNames=false # 唯一包名(已默认启用)
3.3 第三步:升级核心版本号
buildSrc/build.gradle.kts
// AGP 版本
implementation("com.android.tools.build:gradle:9.2.1") // 原 8.6.0
gradle.properties
version_kotlin=2.3.0 # 原 2.1.0
version_ksp=2.3.6 # 原 2.1.0-1.0.29(注意 KSP 2.x 起版本号格式变化)
gradle_plugin/(独立 Gradle 插件项目)
// settings.gradle.kts
kotlin("jvm") version "2.3.0" // 原 2.1.0
// plugins/build.gradle.kts
compileOnly("com.android.tools.build:gradle-api:9.2.1") // 原 8.6.0
compileOnly("com.android:zipflinger:9.2.1") // 原 8.3.0
gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-9.4.1-all.zip
四、详细改动清单
4.1 内置 Kotlin(Built-in Kotlin)迁移
AGP 9 默认启用内置 Kotlin,不再需要显式 apply kotlin-android 插件。若仍显式 apply 会报错:
Cannot add extension with name 'kotlin', as there is an extension already registered with that name.
4.1.1 移除显式插件声明
以下 13 个模块的 plugins {} 块移除了 kotlin-android 相关声明:
| 模块 | 移除的插件 |
|---|---|
buildSrc/src/main/kotlin/hyCommKotlin.gradle.kts |
id("kotlin-android") |
app/build.gradle.kts |
`kotlin-android` |
comm_lib/build.gradle.kts |
id("kotlin-android") |
report_module/build.gradle.kts |
id("kotlin-android") |
push_module/build.gradle.kts |
id("kotlin-android") |
share_module/build.gradle.kts |
id("kotlin-android") |
privacy_check/build.gradle.kts |
id("kotlin-android") |
ui_lib/build.gradle.kts |
id("kotlin-android") |
photoedit_module/build.gradle.kts |
id("kotlin-android") |
as_lib/build.gradle.kts |
id("org.jetbrains.kotlin.android") |
local_third_source/sns_apm_module/build.gradle.kts |
id("org.jetbrains.kotlin.android") |
4.1.2 迁移 kotlinOptions 到 kotlin { compilerOptions {} }
内置 Kotlin 后,android.kotlinOptions {} DSL 不再可用,需迁移到顶层的 kotlin { compilerOptions {} } 块。
修改前:
android {
kotlinOptions {
jvmTarget = "17"
}
}
修改后:
// jvmTarget 默认会跟随 android.compileOptions.targetCompatibility,无需显式声明
kotlin {
compilerOptions {
// 不再硬编码 apiVersion / languageVersion,让 Kotlin 默认跟随项目版本
}
}
坑点:原代码硬编码了
KOTLIN_1_8的apiVersion/languageVersion(2023 年为规避 daemon 警告加的),Kotlin 2.3 最低要求 2.0,会导致Language version 1.8 is no longer supported编译失败。必须删除硬编码,让 Kotlin 跟随项目默认版本。
涉及文件:
buildSrc/src/main/kotlin/hyCommKotlin.gradle.ktsapp/build.gradle.ktslocal_third_source/sns_apm_module/build.gradle.kts
4.2 新 DSL 迁移
AGP 9 移除/重命名了大量旧 DSL,以下是本项目涉及的改动:
| 旧 API | 新 API | 涉及文件 |
|---|---|---|
AppExtension |
ApplicationExtension |
app/build.gradle.kts(函数签名) |
dexOptions {} |
(删除,AGP 9 内部优化) | app/build.gradle.kts |
packagingOptions {} |
packaging {} |
app/build.gradle.kts、local_third_source/bitmap_monitor/build.gradle
|
android.kotlinOptions {} |
kotlin { compilerOptions {} }(顶层) |
多个模块 |
srcDir(...) |
directories += (...) |
app、ui_lib、photoedit_module、6 个 local_repository/*
|
task("xxx") {} |
tasks.register("xxx") {} |
app/build.gradle.kts |
BaseExtension.ndkDirectory |
AndroidComponentsExtension.sdkComponents.ndkDirectory |
app/build.gradle.kts |
jniLibs.srcDir("libs") |
jniLibs.directories += "libs" |
6 个 local_repository/* 模块 |
4.2.1 proguard-android.txt → proguard-android-optimize.txt
AGP 9 不再支持 proguard-android.txt(含 -dontoptimize,阻止 R8 优化):
// 修改前
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
// 修改后
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
4.3 Variant API 迁移
旧 applicationVariants.all {}(eager、sync)→ 新 androidComponents.onVariants { variant -> ... }(lazy、provider-based)。
4.3.1 rename {} 块改造
修改前:
this.applicationVariants.all { variant ->
variant.outputs.each { output ->
def fileName = "${variant.productFlavors[0].name}-${variant.buildType.name}.apk"
output.outputFileName = fileName
}
}
修改后:
androidComponents {
onVariants { variant ->
variant.outputs.forEach { output ->
output.outputFileName.set("${variant.flavorName}-${variant.buildType}.apk")
}
}
}
4.3.2 HySoFilesScan {} 块改造
同上方式改造,访问 variant.flavorName / variant.buildType。
4.3.3 defaultConfig 在 onVariants 中不可访问
在 androidComponents.onVariants 块内无法直接访问 defaultConfig.versionName / versionCode,需通过 variant outputs 获取:
val versionName = variant.outputs
.filterIsInstance<VariantOutputImpl>()
.firstOrNull()?.versionName?.orNull
val versionCode = variant.outputs
.filterIsInstance<VariantOutputImpl>()
.firstOrNull()?.versionCode?.orNull
4.4 Room 升级(2.5.2 → 2.8.4)
4.4.1 升级原因
Room 2.5.2 在 KSP 2.x + Kotlin 2.3 下会崩溃:
last parameter of varargs method detete must be an array
at androidx.room.writer.DaoWriter.createShortcutMethods(DaoWriter.kt:466)
(detete 是 Room 2.5.2 的 typo bug,在新版本已修复)
4.4.2 版本修改
buildSrc/src/main/kotlin/Dependencies.kt:
const val roomRuntime = "androidx.room:room-runtime:2.8.4"
const val roomRxjava2 = "androidx.room:room-rxjava2:2.8.4"
const val roomCompiler = "androidx.room:room-compiler:2.8.4"
4.4.3 Room 2.8 + KSP2 的 Java DAO 可空性 bug
Room 2.8 的 KSP2 处理器对 Java DAO 的响应式返回类型(Flowable / LiveData / Publisher)有可空性推导 bug —— 把 Java platform type Flowable<ProductBean> 推导成 Flowable<ProductBean?>?,但生成的 createFlowable 返回非空,导致 override 签名不匹配。
解决方案:在 ksp {} 块加 arg("room.generateKotlin", "false"),让 Room 生成 Java 代码(*_Impl.java)而非 Kotlin 代码(*_Impl.kt),绕过 Kotlin 严格可空性检查。
// app/build.gradle.kts
ksp {
arg("ksp.test", "aaaa1111")
arg("room.generateKotlin", "false")
}
涉及文件:
app/build.gradle.kts-
report_module/build.gradle.kts(防御性配置)
4.4.4 DAO 可空返回类型调整
Room 2.8 对单实体查询返回类型要求可空(SQL 可能查不到记录)。以下 DAO 的 select() 返回类型加 ?:
| 文件 | 改动 |
|---|---|
app/src/main/java/.../sticker/dao/StickerGroupDao.kt |
fun select(id: String): StickerGroupBean → StickerGroupBean?
|
app/src/main/java/.../tagline/dao/TagTabDao.kt |
fun select(tagId: String): TagTabBean → TagTabBean?
|
app/src/main/java/.../tagline/viewmodel/TagLineViewModel.kt |
调用方加 ?: throw NullPointerException(...) 保持原 onError 兜底语义 |
4.5 enableAppCompileTimeRClass 配置(保持 R 字段为常量)
4.5.1 问题
AGP 9 引入新开关 android.enableAppCompileTimeRClass,默认从 false 变为 true,让 app 模块也使用非 final R class(与 library 对齐)。这导致 R.id.xxx 不再是编译期常量,无法用在 switch 的 case 上:
错误: 需要常量表达式
case R.id.bt_login:
4.5.2 解决方案
项目里有大量 Java 代码使用 switch(R.id.xxx),一次性全改 if-else 风险大。保持 R 字段为常量:
gradle.properties:
android.nonFinalResIds=false
android.enableAppCompileTimeRClass=false
4.5.3 长期建议
AGP 10 会移除该选项,届时必须把所有 switch(R.id.xxx) 迁移成 if-else 链:
- Android Studio 提供自动转换:光标放在
switch上 →Option+Enter→ "Replace switch with if-else" - Kotlin 的
when允许非常量 case,所以只需改 Java 文件 - 可用 lint 的
NonConstantResourceId检查扫描定位
4.6 sourceSets 配置:java 与 kotlin 分离
4.6.1 问题
AGP 9 内置 Kotlin 严格区分 java 和 kotlin source set。原配置只把 hytest 加到了 java source set:
this.java {
directories += "src/hytest/java"
}
旧版 kotlin-android 插件会自动扫 java 目录里的 .kt 文件,新插件不会。导致 hytest 下的 .kt 文件(TestFaceVerifyFragment.kt、TestRxBusFragment.kt 等)不参与编译,Java 调用方报 找不到符号。
4.6.2 解决方案
混合源码目录(同时有 .java 和 .kt)必须显式加到两个 source set:
(this.maybeCreate("debug") as com.android.build.api.dsl.AndroidSourceSet).run {
this.java {
directories += "src/hytest/java"
}
// 关键:AGP 9 内置 Kotlin 不再自动扫 java 目录的 .kt 文件
this.kotlin {
directories += "src/hytest/java"
}
this.res {
directories += "src/hytest/res"
}
}
4.7 文件名与类名一致性
4.7.1 问题
Kotlin 规范要求顶层类名必须与文件名一致。旧 Kotlin 是 warning,Kotlin 2.3 + AGP 9 内置 Kotlin 收紧,会导致该类对其它文件不可见(编译报 找不到符号)。
4.7.2 修复(用 git mv 保留历史,不改代码)
| 原文件名 | 新文件名 | 类名 |
|---|---|---|
app/src/hytest/.../bean/LiveDataEvent.kt |
LiveDataTestEvent.kt |
LiveDataTestEvent |
app/src/main/.../chat/event/KickGroupReusltEvent.kt(有 typo) |
KickGroupResultEvent.kt |
KickGroupResultEvent |
4.8 Kotlin 2.3 语法严格性
GroupChatMsgActivity.kt:260 的 isEmpty 缺括号:
// 修改前(Kotlin 2.3 前容忍)
if (!atUsers.isEmpty) {
// 修改后
if (!atUsers.isEmpty()) {
atUsers 是 SimpleArrayMap,isEmpty() 是方法不是属性。Kotlin 2.3 不再把裸函数名当函数引用容忍。
4.9 模块级 compileSdk 显式声明
AGP 9 要求所有模块显式声明 compileSdk,不能再从其他地方继承。
问题模块:privacy_check/build.gradle.kts(空壳 library,没用 hyCommAndroid 脚本)
android {
namespace = "com.sohu.sohuhy.privacycheck"
compileSdk = AndroidInfo.compileSdk // 新增
defaultConfig {
minSdk = AndroidInfo.minSdk // 新增
}
}
其它模块都用了
hyCommAndroid预编译脚本插件(在buildSrc/src/main/kotlin/hyCommAndroid.gradle.kts里统一设置了compileSdk),无需改动。
五、验证结果
5.1 skill 要求的三步验证
| 验证步骤 | 结果 | 耗时 |
|---|---|---|
| 1. Gradle IDE Sync | ✅ 通过 | 14m 5s |
2. ./gradlew help
|
✅ 通过 | 10s |
3. ./gradlew build --dry-run
|
✅ 通过 | 10m 58s |
5.2 编译验证
| 验证项 | 结果 |
|---|---|
:app:kspFlavorsDevDebugKotlin |
✅ 通过 |
:app:compileFlavorsDevDebugKotlin |
✅ 通过 |
:app:compileFlavorsDevDebugJavaWithJavac |
✅ 通过 |
建议后续完整验证:
./gradlew :app:assembleFlavorsDevDebug并在真机上跑一次冒烟测试。
六、受影响文件清单
6.1 构建配置文件(30 个)
app/build.gradle.kts
as_lib/build.gradle.kts
buildSrc/build.gradle.kts
buildSrc/src/main/kotlin/Dependencies.kt
buildSrc/src/main/kotlin/hyCommKotlin.gradle.kts
comm_lib/build.gradle.kts
gradle.properties
gradle/wrapper/gradle-wrapper.properties
gradle_plugin/gradle/wrapper/gradle-wrapper.properties
gradle_plugin/plugins/build.gradle.kts
gradle_plugin/settings.gradle.kts
local_repository/amap_sdk/build.gradle.kts
local_repository/csjpeg_sdk/build.gradle.kts
local_repository/gifflen_sdk/build.gradle.kts
local_repository/news_ad_sdk/build.gradle.kts
local_repository/speech_sdk/build.gradle.kts
local_repository/video_sdk/build.gradle.kts
local_repository/zxing_sdk/build.gradle.kts
local_third_source/bitmap_monitor/build.gradle
local_third_source/sns_apm_module/build.gradle.kts
photoedit_module/build.gradle.kts
privacy_check/build.gradle.kts
report_module/build.gradle.kts
ui_lib/build.gradle.kts
6.2 源码文件(5 个)
app/src/main/java/hy/sohu/com/app/chat/view/message/GroupChatMsgActivity.kt (isEmpty 语法)
app/src/main/java/hy/sohu/com/app/sticker/dao/StickerGroupDao.kt (DAO 可空)
app/src/main/java/hy/sohu/com/app/tagline/dao/TagTabDao.kt (DAO 可空)
app/src/main/java/hy/sohu/com/app/tagline/viewmodel/TagLineViewModel.kt (调用方兜底)
6.3 文件重命名(2 个,无内容修改)
app/src/hytest/java/.../bean/LiveDataEvent.kt → LiveDataTestEvent.kt
app/src/main/java/.../chat/event/KickGroupReusltEvent.kt → KickGroupResultEvent.kt
6.4 测试源集文件(1 个,可空性注解调整后回退)
app/src/hytest/java/hy/sohu/com/app/test/model/db/ProductDao.java
七、后续注意事项
7.1 已知的技术债
| 项 | 说明 | 建议处理时机 |
|---|---|---|
switch(R.id.xxx) 未迁移 |
依赖 enableAppCompileTimeRClass=false
|
AGP 10 升级前必须完成 |
resourceConfigurations 使用了 deprecated API |
已加 @Suppress("DEPRECATION")
|
后续迁移到 androidResources.localeFilters
|
| 大量 AGP 9 deprecation warnings |
excludeLibraryComponentsFromConstraints 等 |
可在 gradle.properties 加 android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false 静默 |
7.2 Room 2.8 行为变化留意
- 默认使用 KSP2:当前 KSP 2.3.6 已满足要求
-
数据库 schema 重新校验:2.5.2 → 2.8.4 可能重新生成 schema 文件。若有
room.schemaLocation导出的 schema 用于迁移测试,建议跑一遍 ksp 后 diff 一下 - 更严格的 DAO 写法:2.6→2.8 对部分 DAO 写法更严格,出现新警告按提示改
7.3 日常开发注意事项
-
新增模块:无需再声明
kotlin-android插件,AGP 9 自动支持 Kotlin -
新增 Kotlin 源码目录:必须用
this.kotlin { directories += "xxx" },不能只加到java - 新增顶层类:文件名必须和类名完全一致
-
DAO 单实体查询:返回类型记得加
?(可空)
7.4 性能优化建议(可选)
以下配置可进一步提升构建性能,但需评估兼容性:
# 启用非传递 R class(默认 false,因项目内大量跨模块 R 引用,暂不开启)
# android.nonTransitiveRClass=true
# 启用配置缓存(需配合 AGP 9 优化)
# org.gradle.configuration-cache=true
# 启用并行构建
org.gradle.parallel=true
org.gradle.caching=true
八、参考资料
- Android Gradle Plugin 9.0 Release Notes
- Migrate to built-in Kotlin
- Room Releases
- KSP Releases
- Gradle 9.4 Release Notes
九、变更记录
| 日期 | 内容 |
|---|---|
| 2026-06-22 | 完成 AGP 8.6.0 → 9.2.1 主体升级,Sync 通过 |
| 2026-06-22 | 解决代理配置冲突(全局 Gradle / shell / IDE) |
| 2026-06-22 | 完成内置 Kotlin 迁移(13 个模块移除 kotlin-android) |
| 2026-06-22 | 完成 Kotlin 2.3.0 / KSP 2.3.6 版本对齐 |
| 2026-06-22 | 完成 Variant API 迁移(applicationVariants → onVariants) |
| 2026-06-22 | 完成 ./gradlew help 与 ./gradlew build --dry-run 验证 |
| 2026-06-23 | 修复 Kotlin 1.8 硬编码(DaoWriter 崩溃) |
| 2026-06-23 | 升级 Room 2.5.2 → 2.8.4(修复 detete typo bug) |
| 2026-06-23 | 修复 Room 2.8 + KSP2 Java DAO 可空性 bug(room.generateKotlin=false) |
| 2026-06-23 | 修复 proguard-android.txt 不再支持 |
| 2026-06-23 | 修复 enableAppCompileTimeRClass 导致 switch(R.id) 失效 |
| 2026-06-23 | 修复 sourceSets java/kotlin 分离问题(hytest) |
| 2026-06-23 | 修复文件名与类名不一致(LiveDataTestEvent / KickGroupResultEvent) |
| 2026-06-23 | 修复 Kotlin 2.3 语法严格性(isEmpty 缺括号) |
| 2026-06-23 | 全量编译验证通过,输出本文档 |