Flutter Android 迁移配置:切到内置 Kotlin 并校验 AGP 9
很多 Flutter Android 团队已经能稳定跑 flavor、签名和 AAB 验证,但一到 Android Gradle Plugin 9(AGP 9)升级阶段,问题就不再只是“能不能打包成功”。真正麻烦的是,旧项目往往同时混着三类历史包袱:kotlin-android 这类旧 Kotlin Gradle Plugin(KGP)用法、Flutter 3.16 之前的 apply from: 脚本式插件加载、以及依赖旧 BaseExtension / applicationVariants 的第三方 Gradle 逻辑。结果就是团队看到一次构建报错,就把所有问题都归成“Gradle 升级炸了”,最后只能不停回滚。
如果你前一阶段已经按站内这两篇把发布和测试基线补起来:Flutter Android 发布前别只会点 Build:签名、AGP/JDK 兼容与 AAB 验证清单 和 Flutter Android 测试别只跑 flutter run:单元、Widget、集成测试与设备回归清单,那这篇文章要解决的就是更靠后的长期维护问题:把老 Flutter Android 工程从旧 KGP 迁到 built-in Kotlin,并用 AGP 9 的版本边界、构建日志和设备启动结果证明迁移真正完成。
Flutter 官方在 2026 年 8 月发布的 built-in Kotlin app migration guide 已经把边界说得很直白:android.builtInKotlin=true 只有在应用本身和所依赖的 Flutter 插件都迁完后才该打开,而且这一步要求 Flutter 3.47+。Android 官方的 AGP 9.0 release notes 也明确指出,AGP 9 默认启用 built-in Kotlin,并且旧 org.jetbrains.kotlin.android 插件不再兼容新 DSL。换句话说,这次迁移不是“改一行版本号”,而是一次受版本矩阵、插件兼容和验收流程共同约束的工程迁移。
先判断项目是不是这类迁移,不要把纯 Java 工程也硬拖进来
这篇流程只适用于当前 Android 模块已经在用 KGP 的 Flutter 工程。最直接的检查方式,是先在 android/ 目录里搜下面这些标记:
rg -n "kotlin-android|org.jetbrains.kotlin.android|kotlinOptions|applicationVariants|variantFilter" android
看到下面任一情况,就说明你命中了本文场景:
android/app/build.gradle或build.gradle.kts里有id("kotlin-android")、id("org.jetbrains.kotlin.android")- 旧 Groovy 写法里还保留
apply plugin: 'kotlin-android' android { kotlinOptions { ... } }还在 app 模块里- 自定义 Gradle 逻辑还依赖
applicationVariants、variantFilter或BaseExtension
反过来说,如果你的 Android 侧没有 Kotlin 源码,也没有应用 kotlin-android,Flutter 官方文档给出的边界更简单:这类项目不需要做 built-in Kotlin 迁移,只要按 AGP 9 过渡要求先补 android.newDsl=false,让旧 DSL 继续可用即可。很多团队的第一步就做错了,明明只是升级 AGP,却先把所有 Kotlin 配置、插件脚本和私有 Gradle API 一起重写,风险反而比版本升级本身更大。
先把版本和环境基线钉住,再谈代码改动
这一步一定要先做,因为 built-in Kotlin 迁移最容易被“看起来像代码问题,实际上是工具链不对齐”的假象误导。按 Flutter 和 Android 官方文档,至少要确认下面几件事:
- 开启
android.builtInKotlin=true需要 Flutter 3.47 或更高版本;Flutter 3.44 只支持在 AGP 9 下先保留android.builtInKotlin=false - AGP 9.0 的官方兼容矩阵要求最少 Gradle 9.1.0、JDK 17
- 如果你计划直接落到 AGP 9.1.1,Android 官方当前文档列出的最低 Gradle 已是 9.3.1,JDK 仍为 17
建议把验证环境直接写进迁移记录里,不要只截图 IDE:
flutter --version
flutter doctor -v
cd android
./gradlew -v
我更建议在团队里固定一条判断顺序:
- 先看 Flutter 版本够不够 3.47。
- 再看
gradlew -v输出的 Gradle 和 JVM 版本。 - 最后才看
build.gradle里的 AGP 版本。
原因很现实:不少项目把 AGP 版本升上去了,但 CI 机器还是旧 Gradle Wrapper 或旧 JDK,最终报错却出现在 Kotlin 编译期,排查方向会被带偏。把版本基线写清之后,后面无论是本地 Android Studio、命令行还是 CI 失败,都能先排除“环境不一致”。
用两阶段迁移,而不是在一个提交里同时开所有新开关
这次迁移最稳的做法不是一步切,而是拆成两个阶段。
第一阶段先让项目在 AGP 9 上继续稳定构建,也就是暂时保留 Flutter 自动加上的过渡标志:
# android/gradle.properties
android.newDsl=false
android.builtInKotlin=false
如果这两个标志还没出现,Flutter 官方说明可以先跑一次 flutter run 或 flutter build apk,工具会自动补进去;但 add-to-app 的 Android host app 不会自动补,必须手工写入。
第二阶段才是真正切到 built-in Kotlin。在这之前,如果你的项目还是 Flutter 3.16 以前生成的 Groovy 老结构,最好把脚本式插件加载一起迁到 plugins {}。Flutter 官方关于 deprecated imperative apply 的迁移文档说得很明确:老的 apply plugin / apply from: 应替换成声明式 Plugin DSL,这样后续 AGP Upgrade Assistant 和新 DSL 才更容易识别项目结构。
这里还有一个经常被忽略的小边界:settings.gradle 里的 pluginManagement {} 和 plugins {} 必须放在文件最前面,dev.flutter.flutter-plugin-loader 也不能误写成 apply false。很多老项目迁到一半时只顾着改 app/build.gradle,却忘了先把 settings.gradle 调整到 Flutter 官方要求的顺序,最后构建日志看起来像 Kotlin 失败,根因其实是插件加载顺序不合法。
也就是说,推荐顺序不是“看到 built-in Kotlin 文档就立刻把 android.builtInKotlin=true 打开”,而是:
- AGP 9 + 过渡标志先跑通。
- 老
apply语法迁到plugins {}。 - app 模块移除
kotlin-android与kotlinOptions。 - 插件兼容性检查通过后,再把
android.builtInKotlin=true打开。
这个顺序的价值,在于每一步都能对应一类清晰的失败日志,不会把“项目结构没迁完”和“插件没兼容”混成一个错误池。
app 模块只改三类地方,改完就停,不要顺手大扫除
Flutter 官方给出的 app 迁移动作其实很集中,核心只有三类。
第一,删掉 app 模块里的 KGP 应用。无论你是 plugins { id("kotlin-android") },还是 apply plugin: 'kotlin-android',built-in Kotlin 打开后都不应该继续保留。
第二,删掉 android { kotlinOptions { ... } }。官方迁移指南要求把 JVM 目标迁到新的 Kotlin DSL 块里,而不是继续挂在旧 android 扩展下面。
第三,补 kotlin { compilerOptions { ... } }。一个最小可用的 app 模块片段可以像这样:
plugins {
id("com.android.application")
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.app"
compileSdk = 36
}
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
}
}
如果你仍在用旧版 Groovy 工程,结构也一样,只是语法不同。额外有两个容易被忽略的点:
- 如果根
build.gradle里只是单纯声明org.jetbrains.kotlin:kotlin-gradle-plugin供 app 模块使用,迁完后通常可以删掉这条旧依赖 - 如果项目里还有自定义插件、KSP、或必须高于 AGP 默认值的 Kotlin 版本钉死逻辑,就不要顺手删除全部 Kotlin 相关声明,而是先看它究竟是 app 需要,还是第三方插件仍在依赖
迁移阶段最忌讳“顺便统一 Gradle 风格”。只要当前目标是切到 built-in Kotlin,就先收口这三类改动,别同时把仓库重构成 version catalogs、buildSrc 清理、模块拆分三件套。那不是迁移严谨,是制造新的变量。
验收不能只看一次 assemble 成功,要固定日志和设备结果
真正能说明迁移成功的,不是某台开发机点了一次 Run 按钮,而是下面这组命令在本地和 CI 都能重复通过:
flutter clean
flutter pub get
cd android
./gradlew :app:assembleDebug --stacktrace
./gradlew :app:dependencies --configuration debugCompileClasspath
cd ..
flutter build apk --debug
flutter test
flutter run -d emulator-5554
验收时我建议至少记三类结果:
build_success:assembleDebug和flutter build apk --debug都返回 0device_launch_success:应用能在 Android 模拟器或真机启动,不是只停在编译完成legacy_kgp_absent:app 模块里已经搜不到kotlin-android与kotlinOptions
日志上重点看两类失败信号。
第一类是插件未迁移。这时常见表现不是业务代码报错,而是某个 Flutter 插件的 Android 子工程还在套旧 KGP,用 android.builtInKotlin=true 后直接阻断整个构建。这类问题不要靠“降回旧 AGP”糊过去,更稳的做法是先把 android.builtInKotlin=false 留住,升级插件版本或给插件作者提 issue。
第二类是旧 DSL / 旧 Variant API 仍在生效。Android 官方在 AGP 9 文档里给过一个很典型的信号:如果你看到 ApplicationExtensionImpl ... cannot be cast to BaseExtension 这类异常,说明还存在旧 BaseExtension 依赖,不是 built-in Kotlin 本身坏了,而是新 DSL 兼容没有收完。这个时候应该去搜 applicationVariants、variantFilter、私有扩展类型和第三方 Gradle 插件,而不是反复改 Kotlin 代码。
这些失败边界要提前写清,不要等 CI 红了再猜
第一,add-to-app 比纯 Flutter app 多一层 host app 边界。Flutter migrator 不会在纯原生 Android host 工程里自动补标志,gradle.properties 需要手工维护。
第二,AGP Upgrade Assistant 很有用,但它不是万能修复器。Android 官方文档建议在运行前先备份、保持工作区干净,并尽量用声明式 DSL;如果你的版本常量深埋在 buildSrc 或自定义脚本里,工具能识别的范围会明显下降。
第三,android.newDsl=false 和 android.builtInKotlin=true 不是同一个维度。前者是在兜旧 DSL / 旧 Variant API,后者是在切 Kotlin 编译集成。很多团队误以为 built-in Kotlin 打开后就等于全部迁移完成,其实第三方插件还可能卡在旧 DSL 上。按 Android 官方说明,android.newDsl=false 这种 opt-out 能力会在 AGP 10 移除,所以这条线索必须尽早纳入技术债清单。
第四,不要把“能编译”误当成“能发布”。如果你项目本来就有多环境、签名、混淆、上传符号表或设备回归流程,built-in Kotlin 迁完后至少再跑一次 release 相关验证。站内前面的 Flutter Android 多环境配置实战:flavor、dart-define-from-file 与 CI 构建检查清单 可以直接当这一步的补充清单。
总结
Flutter Android 切到 built-in Kotlin,本质上是一次受版本矩阵、插件兼容和验收纪律共同约束的构建迁移。最稳的做法不是直接把 android.builtInKotlin=true 打开碰运气,而是先锁 Flutter / AGP / Gradle / JDK 基线,再拆开过渡标志、Plugin DSL、app 模块 Kotlin 配置和第三方插件边界,最后用构建日志、设备启动和测试结果做收口。
只要你把“旧 KGP 退出 app 主链路”“插件未迁移单独暴露”“旧 DSL 问题单独定位”这三件事分开,AGP 9 迁移就会从一次高噪声升级,变成一套可复盘、可灰度推进、能在团队里复制的长期维护方案。