理解 SDK 版本号的含义与演变
刚开始做 Android 开发那阵子,我跟很多新手一样,对版本号那串数字完全无感。
直到有一天,我手滑把 compileSdkVersion 从 28 改成了 30,结果项目爆出一百多个编译错误……
嗯,从那以后我才意识到,版本号背后藏着不少门道。
版本号结构:Major.Minor.Patch
标准语义化版本号(SemVer)长这样:major.minor.patch。
比如 8.0.0、12.1.3。每个位置的含义很明确:
- Major——不兼容的 API 变更。升级后旧代码很可能要改。
- Minor——向后兼容的功能新增。你可以放心加依赖,一般不会炸。
- Patch——向后兼容的 bug 修复。基本无感升级。
我见过很多项目直接把 minSdkVersion 设成 API 21,结果因为某个第三方库用了 API 24 才有的 API,运行时直接崩。
说白了,版本号的每段数字都是在告诉你:这次改动的风险有多大。
Android 特有的三个版本概念
在 Gradle 配置里,你会看到三个版本号:
compileSdkVersion—— 编译时用的 API 级别minSdkVersion—— 最低兼容的 API 级别targetSdkVersion—— 目标 API 级别(系统据此决定是否启用兼容行为)
这三者的关系,我建议你记住下面这张表:
| 名称 | 作用 | 影响范围 | 常见误区 |
|---|---|---|---|
| compileSdkVersion | 决定你能调用哪些 API | 编译期 | 设得过高,编译通过但旧设备运行时找不到方法 |
| minSdkVersion | 决定 App 能安装的最低系统版本 | 安装 & 运行时 | 设得太低,被迫放弃高版本 SDK 的新特性 |
| targetSdkVersion | 告诉系统「我已针对此版本做过测试」 | 运行时兼容行为 | 忘记更新,导致新系统上的兼容性弹窗不消失 |
举个例子,如果你的 compileSdkVersion = 33,minSdkVersion = 21,targetSdkVersion = 30:
- 编译时你可以使用 API 33 的全部功能。
- 但运行在 API 21 的设备上时,你需要自己判断 API 可用性,否则会
NoSuchMethodError。 - 系统看到
targetSdkVersion = 30,会认为你没有适配 Android 12+ 的行为变更,于是保留旧兼容逻辑(比如文件访问权限、前台服务限制等)。
targetSdkVersion = 28,结果上架 Google Play 时被告知必须更新到 30+。改完后测试一切正常,但发布后收到大量用户反馈「通知栏不显示」。排查了一整天,发现是 Android 12 开始对通知权限做了新的运行时请求,而我 targetSdkVersion 跳到 31 后,旧代码里的权限请求流程没走。从那以后,每次升级 targetSdkVersion 我都老老实实看一遍系统行为变更文档。
版本演变历史一览
Android 从诞生到现在,API 级别一路爬升。我整理了一份快速对照表:
| Android 版本 | API Level | 关键行为变更 (对兼容性影响大的) |
|---|---|---|
| 4.4 KitKat | 19 | WebView 改用 Chromium |
| 5.0 Lollipop | 21 | ART 替代 Dalvik,64 位支持 |
| 6.0 Marshmallow | 23 | 运行时权限模型 |
| 7.0 Nougat | 24 | 多窗口支持,文件共享限制 |
| 8.0 Oreo | 26 | 后台执行限制,通知渠道 |
| 10 | 29 | 分区存储强制化,后台位置权限细化 |
| 12 | 31 | 通知运行时权限,精确闹钟限制 |
| 13 | 33 | 通知权限,附近设备权限,细化的媒体权限 |
| 14 | 34 | 前台服务类型声明,更严格的隐式广播限制 |
注意看,从 API 23 开始,每次大版本都会引入至少一项「必须适配」的行为变更。
如果你长期不更新 targetSdkVersion,总有一天会踩到强制合规的雷。
targetSdkVersion 更新到当年主流版本。不要等商店强制要求,那时候往往只剩两三个月,仓促适配容易出事故。
兼容性策略核心:适配的“三步走”
很多同学问我:到底怎样才算「兼容」?我个人理解就是三件事:
- 正确选择 compileSdkVersion: 官方建议用最新稳定版。你可以拿到新 API 的编译支持,但运行时必须做降级判断。
- 合理设定 minSdkVersion: 根据你的用户群数据来。我习惯看 Google Play 后台的「设备与系统版本」统计,如果某个版本占比低于 1% 且该版本有已知安全问题,果断放弃。
- 逐步推进 targetSdkVersion: 先读行为变更文档,然后在测试分支里把
targetSdkVersion升到目标值,针对每个新限制写适配代码,跑通过后才合并到主分支。
下面这张图帮你梳理版本号与适配流程的关系:
看到这张图,你应该能理解为什么我强调「升级 compileSdkVersion 是最安全的」——它只影响编译,不影响已有设备的行为。而 targetSdkVersion 则是真正的兼容性开关,升级它才意味着你主动拥抱新系统的规则。
gradle.properties 里统一定义版本变量:COMPILE_SDK=34MIN_SDK=21TARGET_SDK=33然后在各模块的
build.gradle 中引用。这样升级的时候只需要改一个地方,避免漏改。
好了,这一章就聊到这儿。版本号组合看似简单,但选不对、配不对,后面适配起来处处是坑。 记住我上面说的三步走,至少能帮你省下 80% 的踩 bug 时间。