3. minSdkVersion 与 targetSdkVersion 冲突

这又是一个我经常在项目里遇到的“低级却致命”的配置问题。说白了,就是你在 build.gradle 里写的那两行版本号,让 AGP 在编译时直接罢工了。

我记得有一次,团队新来的同事接手一个老项目,想升级一下 targetSdkVersion 去适配新特性。结果一编译,啪,报错了。他盯着屏幕看了半天,说:“我明明改了一个数字啊,怎么就不行了?”我过去一看——好家伙,targetSdkVersion 改成了 33,但 minSdkVersion 还是 34。你想想看,min 比 target 还大,这逻辑上就不通嘛。

3.1 冲突的两种典型场景

这种冲突其实就两种,你对照一下自己的配置,八成是其中之一:

场景配置值示例AGP 反应
minSdkVersion > targetSdkVersionmin=34, target=33直接报错,无法编译
targetSdkVersion < minSdkVersion(反逻辑但可能因误写出现)min=26, target=24同样报错,提示范围无效

有些同学可能会问:“为什么 AGP 不自动帮我修正?”嗯,AGP 的哲学是:你写的配置就是你明确的意图,它不会替你做主。你写错了,它就停下来告诉你。

警告:两种场景都会导致 Gradle 构建失败,错误信息中会明确提到 minSdkVersion 和 targetSdkVersion 的值顺序。千万别忽略这个提示,直接去查别的依赖问题——我见过有人在这上面浪费了半小时。

3.2 错误信息长什么样?

下面是我从一次真实构建中摘出来的错误日志,你可以直接对着看:

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDebugMainManifest'.
> Manifest merging failed. Uses-sdk:minSdkVersion 34 cannot be greater than targetSdkVersion 33.

注意看最后一行:minSdkVersion 34 cannot be greater than targetSdkVersion 33。AGP 把问题说得已经不能再直白了。

3.3 为什么会产生这个冲突?

我总结了几个常见的“作案动机”:

  • 手抖或复制粘贴错误:改 targetSdkVersion 时,不小心把 minSdkVersion 也改了。这个在多人协作的项目里特别常见。
  • 模块间的版本依赖覆盖:你主模块设的 minSdkVersion 是 26,但某个 library 在它的 build.gradle 里声明了 minSdkVersion 28,而且你的主模块没有明确统一配置,最终合并时 library 的约束覆盖了你的配置,导致冲突。
  • 自动化脚本的 bug:有些团队用脚本批量升级版本号,脚本逻辑写错了,把 min 和 target 的值写反了。我亲眼见过一个脚本把 minSdkVersion 21 写成了 minSdkVersion 31,target 还是 30,直接炸了。

3.4 解决方案

解决方法其实就两步。第一步,确认你真正想要的范围;第二步,把数值改成合理的大小。但细节上有些坑,我挨个说:

3.4.1 手动修正数值

最简单粗暴:打开主模块的 build.gradle(或者 build.gradle.kts),把 minSdkVersion 和 targetSdkVersion 改对。记住一个原则:minSdkVersion ≤ targetSdkVersion,且 targetSdkVersion ≤ 当前最新的 API 级别。

// 正确的写法示例
android {
    compileSdkVersion 34
    defaultConfig {
        minSdkVersion 26
        targetSdkVersion 34
    }
}
我的习惯:我一般会在项目根目录的 gradle.properties 里定义两个变量,比如 MIN_SDK_VERSION=26 和 TARGET_SDK_VERSION=34,所有模块统一引用。这样改一处,全项目同步,不容易出错。

3.4.2 检查 Library 的覆盖

如果主模块的值是对的,但构建依然报错,那就是某个 library 在搞鬼。你需要检查所有 build.gradle 文件,尤其是那些声明了 minSdkVersion 的 library。我建议的做法是:在 library 里不要硬编码 minSdkVersion,而是通过 consumerProguardFiles 或 lintOptions 来配置。实在不行,可以在主模块里用 overrideLibrary 强制替换——但这招有风险,慎用。

android {
    // 如果你必须覆盖 library 的 minSdkVersion,可以这样:
    defaultConfig {
        minSdkVersion 26
        targetSdkVersion 34
    }
}

其实,AGP 在合并 Manifest 时会自动把 library 的 uses-sdk 合并进来,如果主模块的 minSdkVersion 比 library 的小,它不会报错;反过来,如果 library 的 minSdkVersion 比主模块的大,那就会冲突。所以除非你故意要限制更低版本,否则不要在 library 里写死 minSdkVersion。

3.4.3 使用 Gradle 依赖管理工具

如果你用的是 AGP 7.0+,可以参考官方文档,用 buildFeatures 或 lintChecks 来全局统一版本。我个人比较喜欢在 build.gradle 里写一段脚本,在构建前自动检查数值合理性:

// 检查 min 是否大于 target
afterEvaluate {
    android.applicationVariants.all { variant ->
        variant.mergedFlavor.minSdkVersion?.apiLevel?.let { min ->
            variant.mergedFlavor.targetSdkVersion?.apiLevel?.let { target ->
                if (min > target) {
                    throw new GradleException(
                        "minSdkVersion ($min) > targetSdkVersion ($target) — 请修正!"
                    )
                }
            }
        }
    }
}

把这个脚本放在主模块的 build.gradle 底部,一旦数值不合规,Gradle 会在配置阶段就挂掉,报错信息里还会告诉你是哪个模块、哪个取值。这是我踩过坑之后加上的“防呆”机制,现在团队里每个人都很喜欢。

3.5 本章流程图

minSdkVersion 与 targetSdkVersion 冲突分析 build.gradle 配置 minSdkVersion targetSdkVersion 检查数值大小 min > target target < min AGP 构建失败 解决方案 手动修正数值 检查 Library 覆盖 添加 Gradle 检查脚本

3.6 避坑清单

嗯,最后我给你列个清单,每次改版本号之前瞄一眼,能省不少时间:

  • ✅ 确认 minSdkVersion ≤ targetSdkVersion,且 targetSdkVersion 不超过当前 compileSdkVersion。
  • ✅ 检查所有 library 模块的 build.gradle,避免硬编码 minSdkVersion。
  • ✅ 使用统一变量(如 ext.minSdk = 26)集中管理。
  • ✅ 在 afterEvaluate 中添加数值校验脚本,自动拦截非法配置。
  • ✅ 如果使用 Gradle 版本目录(libs.versions.toml),同样要保证版本号逻辑正确。
核心一句话:minSdkVersion 是“向下兼容的地板”,targetSdkVersion 是“当前瞄准的天花板”。地板不能高于天花板,否则你没法站。