Android版本选择全指南:SDK、AGP、Gradle与JDK兼容配置

发布时间:2026/11/3 22:12:08
Android版本选择全指南:SDK、AGP、Gradle与JDK兼容配置

做 Android 项目这些年,被问得最多的问题里,“版本到底怎么选”绝对排得上前三。不管是刚入行的新人,还是带过几个项目的老人,几乎都在这个坑里栽过跟头。我见过太多团队,项目启动时随手填了个 compileSdk、targetSdk,Gradle 插件版本也是复制粘贴来的,结果做到一半发现某个依赖库死活拉不下来,或者打包时冒出一堆莫名其妙的资源冲突,再或者上线后在某些机型上直接闪退。回头一查,全是版本没对齐惹的祸。

这篇内容就是想把“Android 项目开发时版本选择”这件事彻底讲透。它不是一个简单的“填个数字”的动作,而是一整套涉及 SDK 版本、构建工具版本、依赖库版本、JDK 版本之间相互咬合的决策体系。我会从整体设计思路讲起,把每个版本参数背后的含义、相互之间的约束关系、以及实际项目里怎么定这套组合讲清楚,再配上可直接抄作业的配置模板和踩坑排查表。不管你是刚接触 Android 构建体系的新手,还是想把手头项目版本治理理顺的老手,都能从里面找到能直接用的东西。

1. 版本选择这件事的整体设计思路

很多人把版本选择理解成“选个最新的就行”,这个认知本身就是最大的坑。Android 的构建体系是一张网,SDK 版本、构建工具版本、依赖库版本、JDK 版本、甚至 Android Studio 的版本,彼此之间有严格的兼容区间。你动一个,可能牵动一片。所以第一步不是急着填数字,而是先建立一套“版本决策”的思维框架。

1.1 为什么版本选择是个系统工程而不是单点决策

先说说 Android 构建到底是怎么跑起来的。你写的代码经过编译、资源处理、打包、签名,最终变成一个 APK 或 AAB。这条链路上,每一环都有版本参与:JDK 负责编译 Java/Kotlin 代码,Gradle 负责调度整个构建流程,Android Gradle Plugin(简称 AGP)负责把 Android 特有的构建逻辑接进 Gradle,compileSdk 决定你能调用哪些系统 API,targetSdk 决定系统以什么行为模式对待你的应用,minSdk 决定应用能装到多老的设备上,而各种依赖库又各自声明了自己要求的 compileSdk 和 AGP 下限。

这些版本之间不是并列关系,而是有依赖方向的。举个最典型的例子:AGP 版本决定了它支持的 Gradle 版本区间,也决定了它要求的 JDK 版本,还决定了它能配合的最高 compileSdk。你如果选了 AGP 8.x,那 JDK 就必须是 17,Gradle 也得在 8.x 的对应区间里,compileSdk 一般也要跟上 34 左右。反过来,如果你因为某些原因必须用 JDK 11,那 AGP 就只能停在 7.x 系列,compileSdk 也就被限制在 33 上下。这就是为什么我说它是系统工程——任何一个点的选择,都会沿着依赖链传导出去。

我一般建议团队在项目启动阶段就把这张“版本约束图”画出来,哪怕只是写在文档里。因为一旦项目跑起来,后面再想动版本,成本会高得多。尤其是当依赖库多起来之后,某个库悄悄把 compileSdk 要求提到了 34,而你项目还停在 33,构建就会直接报错,报错信息还不一定直白,新手很容易卡在这里。

1.2 四类版本参数的分层理解

为了把这件事讲清楚,我把项目里会碰到的版本参数分成四层,从下往上依次是:运行环境层、构建工具层、编译目标层、依赖库层。这个分层不是官方定义,是我自己梳理下来觉得最好理解的一种方式。

运行环境层包括 JDK 版本和 Gradle 版本。这两个是“地基”,决定了构建能不能跑起来。JDK 是 Java 运行环境,Gradle 本身也是跑在 JVM 上的,所以 JDK 版本选错,Gradle 直接起不来。这一层的选择相对刚性,主要看 AGP 的要求。

构建工具层就是 AGP 版本,它是连接 Gradle 和 Android 构建逻辑的桥梁。AGP 的版本节奏跟 Android Studio 是绑定的,一般升级 Android Studio 时会提示你升 AGP。这一层的选择要考虑团队用的 IDE 版本、CI 环境的稳定性,不能盲目追新。

编译目标层包括 compileSdk、targetSdk、minSdk 三个。compileSdk 是“我用哪个版本的 SDK 来编译”,targetSdk 是“我告诉系统我适配到了哪个版本”,minSdk 是“我最低支持到哪个版本”。这三个经常被混淆,后面我会专门拆开讲。

依赖库层就是各种第三方库和官方库的版本。这一层最容易被忽视,因为库的版本是跟着库自己走的,但它对上面三层有硬性要求。一个库如果要求 compileSdk 34,你项目是 33,那就没得商量,要么升项目,要么换库版本。

把这四层想清楚,你在做版本决策时就不会东一榔头西一棒子,而是知道先定哪层、再定哪层。我的习惯是先定运行环境层(JDK + Gradle),再定构建工具层(AGP),然后定编译目标层,最后用依赖库层去校验和微调。这个顺序的好处是,每一层定下来之后,下一层的可选范围就自动收窄了,决策成本大幅降低。

1.3 稳定优先还是尝鲜优先:团队场景下的取舍逻辑

版本选择还有一个绕不开的取舍:到底用稳定版还是尝鲜版。这个问题没有标准答案,得看项目阶段和团队情况。

对于大多数业务项目,我的建议是“稳定优先,滞后一个小版本”。意思是,当 AGP 或 compileSdk 出了新版本,不要第一时间跟,等它发布一两个补丁版本、社区反馈稳定了再升。原因很现实:新版本刚出来时,各种兼容性问题、构建脚本不兼容、依赖库还没跟上,这些坑都要有人先踩。业务项目的时间成本经不起这么折腾。

但如果是技术预研项目、或者团队有专门的基建同学维护构建体系,那适当尝鲜是合理的,可以提前发现迁移成本,为后续升级铺路。我待过的一个团队就是每季度做一次版本升级演练,在独立分支上把 AGP、compileSdk 都升到最新,跑一遍完整构建和测试,记录下所有报错和改动点。这样等真正要升的时候,心里有底,不会手忙脚乱。

还有一个容易被忽略的点是 CI 环境的版本一致性。本地开发用一套版本,CI 上用另一套,这是很多“本地能跑、CI 挂掉”问题的根源。所以版本选择不只是本地的事,要把 CI 的 JDK、Gradle 缓存、构建镜像都纳入考虑。我一般会在项目根目录放一个版本清单文件,本地和 CI 都从同一个地方读版本号,保证一致性。

2. 核心版本参数逐个拆解与实操要点

上一节讲了整体框架,这一节把每个核心参数单独拎出来讲透。每个参数我都会说清楚它是什么、怎么选、选错了会怎样,以及实际配置时要注意什么。这部分是整篇内容的核心,建议对照着自己项目的配置文件看。

2.1 compileSdk、targetSdk、minSdk 到底怎么区分

这三个参数是新手最容易搞混的,我见过有人把 targetSdk 当成 compileSdk 填,也见过有人以为 minSdk 越低越好。先把定义摆清楚。

compileSdk是编译时使用的 Android SDK 版本。它决定了你的代码能调用哪些系统 API。比如你想用某个在 API 34 才引入的新接口,那 compileSdk 就必须是 34 或更高,否则编译器直接报“找不到符号”。它只影响编译,不影响运行时行为。你可以理解为“我参考的是哪一版说明书来写代码”。

targetSdk是告诉系统“我这个应用已经适配到了哪个版本”。系统会根据这个值决定用哪套行为模式来对待你的应用。比如从某个版本开始,系统对后台权限、通知、存储访问的行为有变化,如果你的 targetSdk 达到了那个版本,系统就会按新行为执行;如果没达到,系统会走兼容模式。所以 targetSdk 直接关系到应用在新系统上的实际表现,是上架应用市场时的硬性要求,各大应用市场都会要求 targetSdk 达到一定版本以上。

minSdk是应用支持的最低系统版本。低于这个版本的设备装不上你的应用。它决定了你的用户覆盖面,也决定了你能用哪些 API 而不用做版本判断。minSdk 定得越低,覆盖的设备越多,但你要做的兼容处理也越多,而且很多新库会要求较高的 minSdk。

三者的关系可以这样理解:minSdk 是地板,targetSdk 是天花板的行为标准,compileSdk 是你手里的工具书版本。compileSdk 一般要大于等于 targetSdk,因为你要用新版 SDK 编译才能声明适配到那个版本。minSdk 则要小于等于 targetSdk。

实际选择时,我的经验是这样:compileSdk 跟着 AGP 和依赖库的要求走,一般用当前稳定版的最新 SDK;targetSdk 跟着应用市场的要求走,市场要求多少就至少填多少,可以适当超前;minSdk 则要结合用户数据和依赖库要求来定,现在主流做法是定在 21 到 24 之间,太低会拖累依赖库选择,太高会丢用户。

注意:compileSdk 升级后,一定要同步检查所有依赖库是否支持。有些老库在新 compileSdk 下会有资源冲突或 API 变更导致的编译错误,这时候要么升级库版本,要么找替代方案。

2.2 AGP 与 Gradle 的版本咬合关系

AGP 和 Gradle 的关系是“插件与宿主”。Gradle 是通用构建工具,AGP 是跑在 Gradle 上的 Android 专用插件。所以 AGP 对 Gradle 版本有明确的最低要求,也有最高兼容限制。这个对应关系在官方文档里有表格,但很多人不看,直接复制别人的配置,结果版本对不上,构建报错。

我整理了一份常见版本的对应关系,方便对照。需要说明的是,这只是基于常见实践的归纳,具体以官方发布说明为准。

AGP 版本最低 Gradle 版本推荐 JDK 版本建议 compileSdk
7.0.x7.0.21131
7.4.x7.51133
8.0.x8.01733
8.1.x8.01734
8.2.x8.21734
8.3.x8.41734

从这张表能看出一个明显的分水岭:AGP 8.0 开始强制要求 JDK 17。这是很多团队升级时最大的拦路虎,因为本地开发环境和 CI 环境都要跟着换 JDK。我见过有团队为了不升 JDK,硬是把 AGP 卡在 7.4,结果新依赖库用不了,最后还是要升,白白浪费了时间。

配置 AGP 版本的位置在项目根目录的build.gradle(或build.gradle.kts)里的plugins块或buildscript块。Gradle 版本则在gradle/wrapper/gradle-wrapper.properties文件里的distributionUrl指定。这两个地方要一起改,只改一个会出问题。

// 项目根目录 build.gradle 片段 plugins { id 'com.android.application' version '8.2.2' apply false id 'com.android.library' version '8.2.2' apply false }
# gradle/wrapper/gradle-wrapper.properties distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip

提示:升级 AGP 时,先改 Gradle wrapper 版本,再改 AGP 版本,顺序反了容易在同步阶段就报错,排查起来更麻烦。

2.3 JDK 版本:被低估的构建地基

JDK 版本在版本选择里经常被忽略,因为很多人觉得“能编译就行”。但 AGP 8.x 强制 JDK 17 之后,这个参数的重要性一下子凸显出来了。JDK 版本不对,Gradle 可能直接启动失败,报错信息往往是“Unsupported class file major version”之类,新手看了完全摸不着头脑。

JDK 版本的选择逻辑很简单:跟着 AGP 的要求走。AGP 7.x 及以下用 JDK 11 就行,AGP 8.x 必须 JDK 17。这里要注意的是,JDK 版本有三个地方要统一:本地开发环境的 JAVA_HOME、Android Studio 里配置的 Gradle JDK、CI 构建环境用的 JDK。这三处不一致,就会出现“本地能跑 CI 挂”的经典问题。

还有一个细节是 Java 语言级别的配置。在模块的build.gradle里,compileOptions和kotlinOptions里的jvmTarget要和 JDK 版本匹配。比如用 JDK 17,那sourceCompatibility、targetCompatibility、jvmTarget都应该是 17。如果这里填的还是 1.8,虽然有时能编译通过,但会遇到一些奇怪的字节码问题。

// 模块级 build.gradle 片段 android { compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = '17' } }

我踩过的一个坑是:本地装了多个 JDK 版本,环境变量指向的是 11,但 Android Studio 里 Gradle JDK 配的是 17,结果命令行构建和 IDE 构建行为不一致,排查了半天才发现是 JDK 来源不同。所以建议团队统一用版本管理工具管理 JDK,或者在项目文档里明确写清楚用哪个 JDK 路径。

2.4 依赖库版本:最容易被忽视的约束源

依赖库版本是版本选择里最动态、最难控的一环。因为每个库都有自己的发布节奏和版本要求,你项目里几十个依赖,任何一个升级都可能带来连锁反应。尤其是那些传递依赖,你直接依赖的库 A 可能又依赖了库 B 的某个版本,而库 B 又对 compileSdk 有要求,这种间接约束很容易被忽略。

处理依赖库版本,我的核心原则是“统一管理、显式声明、定期体检”。统一管理是指把所有依赖版本集中到一个地方,比如用 Gradle 的 version catalog(libs.versions.toml)或者根目录的ext块,避免各个模块各写各的。显式声明是指不要依赖传递依赖的版本,重要的库要自己显式指定版本,防止被其他库的传递依赖带偏。定期体检是指定期跑./gradlew dependencies看依赖树,发现版本冲突及时处理。

版本冲突的典型表现是构建时报“Duplicate class”或者运行时找不到方法。前者是同一个类出现在多个依赖里,后者是编译时用的版本和运行时用的版本不一致。排查这类问题,./gradlew :app:dependencies是必备命令,它能打印出完整的依赖树,标出哪些版本被提升了、哪些被降级了。

# 查看 app 模块的依赖树 ./gradlew :app:dependencies --configuration releaseRuntimeClasspath

对于依赖库版本的选择策略,我一般分三类处理:官方库(如 AndroidX 系列)跟着 compileSdk 走,用较新的稳定版;主流第三方库(如网络、图片加载类)用社区验证充分的稳定版,不追最新;小众库或长期不维护的库,要重点评估,看它要求的 compileSdk 和 AGP 下限是否和项目匹配,不匹配就考虑替换。

注意:依赖库升级后一定要跑完整回归测试,尤其是涉及网络、存储、权限的库。有些库的小版本升级会悄悄改变默认行为,不测根本发现不了。

3. 从零搭建一套版本配置的完整实操

前面讲了原理和参数,这一节把整套流程串起来,从零开始搭一套可用的版本配置。我会按实际操作顺序走一遍,每一步说清楚做什么、为什么这么做、怎么验证。这套流程适用于新项目启动,也适用于老项目的版本治理。

3.1 第一步:确定 JDK 与 Gradle 基线

动手配任何东西之前,先把 JDK 和 Gradle 定下来。这一步的输入是“团队现有环境”和“目标 AGP 版本”。假设我们要用 AGP 8.2.x,那 JDK 必须是 17,Gradle 至少 8.2。

先确认本地 JDK。命令行执行java -version,看输出是不是 17。如果不是,装一个 17 的 JDK,把JAVA_HOME指过去。然后在 Android Studio 的 Settings 里,找到 Build Tools 下的 Gradle,把 Gradle JDK 也设成 17。这两处必须一致。

接着配 Gradle wrapper。在项目根目录执行gradle wrapper --gradle-version 8.2,或者直接改gradle-wrapper.properties里的distributionUrl。改完之后执行./gradlew --version,确认输出的 Gradle 版本和 JVM 版本都对。

这一步的验证标准很简单:./gradlew --version能正常输出,且 JVM 显示 17。如果报错,大概率是 JDK 没配对,回去检查JAVA_HOME和 IDE 配置。

3.2 第二步:锁定 AGP 与 compileSdk 组合

JDK 和 Gradle 就位后,配 AGP。在根目录build.gradle的plugins块里声明 AGP 版本,模块级build.gradle里应用插件。然后配 compileSdk,一般用当前稳定版的最新 SDK,比如 34。

这里有个实操细节:compileSdk 升级后,Android Studio 可能会提示你安装对应版本的 SDK Platform。别跳过,一定要装,否则编译时会报找不到 android.jar。装完之后,同步项目,看有没有报错。

同步通过后,先别急着写业务代码,跑一次空构建./gradlew assembleDebug,确认整条链路是通的。这一步能提前暴露版本不匹配的问题,比写到一半再发现要好得多。

3.3 第三步:配置 targetSdk 与 minSdk 的取值

targetSdk 的取值看应用市场要求。假设当前市场要求 targetSdk 至少 33,那我们就填 33,或者直接填 34 超前一点。填之前要想清楚:targetSdk 提升会带来行为变更,比如权限申请方式、后台限制、存储访问规则等。所以提升 targetSdk 不是改个数字就完事,要对照官方的行为变更清单逐项检查。

minSdk 的取值要结合用户数据。如果应用的目标用户里还有不少老设备,minSdk 可以定低一点,比如 21。但要注意,minSdk 定得低,一些新库就用不了,因为它们可能要求 minSdk 23 或更高。我的做法是先看核心依赖库的 minSdk 要求,取其中最高的作为项目 minSdk 的下限,再结合用户数据往上调。

配置位置在模块级build.gradle的defaultConfig块里:

android { compileSdk 34 defaultConfig { minSdk 24 targetSdk 34 // 其他配置 } }

3.4 第四步:依赖库版本的统一收口

依赖库版本管理,我强烈建议用 version catalog。它是 Gradle 官方提供的集中管理依赖版本的方式,配置文件是gradle/libs.versions.toml。把所有依赖的版本、库坐标、插件都写在这个文件里,各模块引用别名即可。好处是版本一目了然,改一处全局生效,还能被 IDE 自动补全。

一个典型的libs.versions.toml长这样:

[versions] agp = "8.2.2" kotlin = "1.9.22" coreKtx = "1.12.0" appcompat = "1.6.1" material = "1.11.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

模块里引用时写implementation(libs.androidx.core.ktx)即可。这样所有版本集中在一处,升级时只改 toml 文件,清晰可控。

配好之后,跑./gradlew :app:dependencies检查依赖树,重点看有没有版本冲突标记(通常显示为->表示版本被提升)。如果有冲突,根据实际情况决定是统一版本还是排除某个传递依赖。

3.5 第五步:构建验证与版本清单固化

所有配置完成后,做一轮完整验证:清理构建./gradlew clean,然后./gradlew assembleDebug和./gradlew assembleRelease都跑一遍,确保 debug 和 release 都能出包。再跑一遍单元测试./gradlew test,确认没有因版本变更导致的测试失败。

验证通过后,把当前这套版本组合固化下来,写进项目文档或者 README。内容包括:JDK 版本、Gradle 版本、AGP 版本、compileSdk、targetSdk、minSdk、核心依赖库版本。这份清单是后续升级的基线,也是新人接手项目时的第一手资料。

我还会在 CI 配置里加一个检查步骤,构建前先打印./gradlew --version和关键版本号,方便出问题时快速定位环境差异。这个习惯帮我省了很多排查时间。

4. 版本问题排查实录与避坑清单

版本相关的报错,很多都长得差不多,但根因各不相同。这一节我把实际项目中遇到过的典型问题整理出来,配上排查思路和解决方法,做成速查表,方便对照使用。

4.1 常见报错与根因对照表

报错关键词可能根因排查方向解决方式
Unsupported class file major versionJDK 版本与 AGP/Gradle 不匹配检查./gradlew --version的 JVM 版本统一 JDK 到 AGP 要求的版本
Minimum supported Gradle version is XGradle 版本低于 AGP 要求看 AGP 版本对应的最低 Gradle升级 gradle-wrapper 版本
Duplicate class依赖库版本冲突跑依赖树看重复类来源排除传递依赖或统一版本
Failed to resolve: com.xxx依赖库版本不存在或仓库配置问题检查版本号拼写和仓库地址修正版本号或添加仓库
compileSdkVersion is not specified模块未配置 compileSdk检查模块 build.gradle补上 compileSdk 配置
This version of the Android Support plugin requires...AGP 与 IDE 版本不匹配看 IDE 版本和 AGP 要求升级 IDE 或降 AGP
resource linking failedcompileSdk 与依赖库资源不兼容看报错涉及的资源来源升级 compileSdk 或降依赖库版本

这张表覆盖了大部分高频问题。实际排查时,我的习惯是先看报错的第一行和最后一行,第一行往往是根因,最后一行是表象。然后对照表格定位方向,再用具体命令验证。

4.2 三个真实踩坑案例的复盘

第一个坑是 JDK 版本不一致导致的“本地能跑 CI 挂”。当时项目本地用 JDK 17,CI 镜像里默认是 JDK 11,构建时报了一堆看不懂的字节码错误。排查了半天才发现是 CI 环境 JDK 没配对。后来我们在 CI 脚本里显式指定 JDK 路径,并在构建前打印版本信息,这类问题再没出现过。

第二个坑是依赖库传递依赖引发的 compileSdk 冲突。项目 compileSdk 是 33,某个新引入的库传递依赖了一个要求 compileSdk 34 的库,构建直接失败。报错信息指向的是那个传递依赖的库,但我们在自己的依赖声明里根本找不到它。最后靠./gradlew :app:dependencies一层层往上找,才定位到是哪个直接依赖带进来的。解决方式是把那个直接依赖降到不要求 34 的版本。

第三个坑是 targetSdk 提升后的运行时行为变化。项目把 targetSdk 从 32 提到 33 后,测试发现通知权限的申请逻辑失效了。原因是新版本对通知权限的处理方式变了,需要显式申请。这种问题编译期发现不了,只能靠测试覆盖。所以提升 targetSdk 一定要配完整的回归测试,尤其是涉及权限、存储、后台的模块。

4.3 版本升级的稳妥操作节奏

版本升级最怕“一把梭”,改一堆版本然后构建失败,根本不知道是哪个改动引起的。我的建议是分步走,每步验证。

先升 Gradle wrapper,单独验证构建能跑通。再升 AGP,验证同步和构建。然后升 compileSdk,验证编译和资源处理。接着升 targetSdk,跑回归测试。最后升依赖库,逐个升、逐个测。每一步都提交一次代码,出问题能快速回滚到上一个可用状态。

这个节奏看起来慢,但实际比“改一堆再排查”要快得多。我见过团队一次性把所有版本升到最新,结果构建报了几十个错,排查了两天才理清楚,反而更费时间。

提示:升级前先在独立分支操作,主分支保持稳定。升级分支上每完成一步就提交,形成清晰的提交历史,方便回溯。

4.4 团队协作下的版本一致性保障

最后说说团队协作。版本问题在单人开发时还好,多人协作时会被放大。A 同学本地 JDK 17,B 同学 JDK 11,C 同学的 IDE 又配了另一套,三个人构建行为都不一样,代码合并后问题频出。

保障一致性的核心是“版本来源唯一”。JDK 版本写进项目文档和 CI 配置,Gradle 版本由 wrapper 锁定(wrapper 文件要提交到仓库),AGP 和依赖库版本由 version catalog 统一管理。新人入职第一件事就是按文档配环境,配完跑一次./gradlew --version和空构建验证。

另外,可以在项目里加一个简单的版本检查脚本,构建前自动比对关键版本是否符合预期,不符合就给出提示。这个脚本不复杂,但能挡住很多低级问题。

#!/bin/bash # 简单的版本检查脚本示例 expected_jdk="17" actual_jdk=$(java -version 2>&1 | head -1 | grep -oE '[0-9]+' | head -1) if [ "$actual_jdk" != "$expected_jdk" ]; then echo "警告:当前 JDK 版本为 $actual_jdk,项目要求 $expected_jdk" exit 1 fi echo "JDK 版本检查通过"

这套机制跑顺之后,版本问题基本就从“反复踩坑”变成了“偶发可控”。我个人在实际操作中的体会是,版本选择这件事,前期多花半天把基线定清楚,后期能省下好几天的排查时间。尤其是团队项目,把版本清单和检查机制建起来,比任何单点的技术技巧都管用。