目录
一、什么是API级别
二、MinSdkVersion
2.1 概述
2.2 作用:
2.2.1 安装检测
2.2.2 API 可用性/兼容性检测
2.3 如何选择版本
三、CompileSdkVersion
3.1 概述
3.2 作用
3.3 如何选择版本
四、TargetSdkVersion
4.1 概述
4.2 作用
4.3 如何选择版本
五、总结
对 Android 项目来说,MinSdkVersion、CompileSdkVersion、TargetSdkVersion 这三个属性是必不可少的,贯穿于应用开发的始终,它们的作用也非常重要,只有搞清楚它们三者的内在含义和逻辑关系才能「正确」地设定或修改它们的数值,以确保应用在不同版本的 Android 系统上正常的运行。
以下是笔者新建项目(开发环境:Android Studio Dolphin | 2021.3.1 Patch 1 MacOS)的配置,其中 minSdk 需要创建新项目过程中在 SDK 版本列表里自己选择,compileSdk 和 targetSdk 都是系统默认分配的,它自动和最新的SDK版本保持一致。
android {
namespace 'com.example.myapplication'
compileSdk 32
defaultConfig {
applicationId "com.example.myapplication"
minSdk 26
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
MinSdkVersion、CompileSdkVersion、TargetSdkVersion 设置的整数值都唯一对应一个API 级别,所以要搞懂它们,最好要先理解什么是 API 级别。
API 级别是对 Android 平台版本提供的框架 API 修订版进行唯一标识的整数值。Android 平台提供了一套框架 API ,应用可利用它与底层 Android 系统进行交互。该框架 API 由以下部分组成:
- 一组核心软件包和类
- 一组用于声明清单文件的 XML 元素和属性
- 一组用于声明和访问资源的 XML 元素和属性
- 一组 intent
- 一组应用可请求的权限,以及系统中包括的权限强制执行
Android 平台的每个后续版本均可包括其提供的 Android 应用框架 API 的更新。
框架 API 的更新是为了让新 API 与早期版本的 API 保持兼容。换言之,大多数 API 更改都是新增功能,和引入新的或替代功能。在 API 的某些部分得到升级时,系统替换部分旧版的 API,但不会将其移除,以便其仍可供现有应用使用。在极少数情况下,系统可能会修改或移除部分 API,但通常只有在为确保 API 稳健性以及应用或系统安全性时,才需要进行此类更改。所有其他早期修订版的 API 都将继续保留,不做任何修改。
Android 平台提供的框架 API 由被称作“API 级别”的整数标识符指定。每个 Android 平台版本恰好支持一个 API 级别,但隐含对所有早期 API 级别(低至 API 级别 1)的支持。Android 平台初始版本提供的是 API 级别 1,后续版本的 API 级别则依次增加。
API LEVEL 的数值不断增加,展示了一个“持续优化”并保持“前向兼容”的过程,MinSdkVersion、CompileSdkVersion、TargetSdkVersion 三者可以协助我们在项目开发过程中更好的平衡“更新”与“兼容”。
参考: 不同的 API 级别
用于定义应用程序支持的最低 API 版本,或者说应用运行所需最低 API 级别的整数。如果系统的 API 级别低于该属性中指定的值,Android 系统将阻止用户安装应用。
注意:如果不声明该属性,系统将假定默认值为“1”,这表示该应用兼容所有 Android 版本。如果应用不能兼容所有版本(例如,程序里使用了API 级别 3 中才引入的 API),但并未声明正确的 minSdkVersion
,则当应用安装在 API 级别小于 3 的系统上时,应用在运行时尝试访问不可用的 API 时会发生崩溃。举个具体的例子:checkSelfPermission(检查权限方法)是 API 23才加入的,在Android 6.0(API 23)系统的手机会正常运行,假如 minSdkVersion 设为22,程序可以正常安装到
Android 5.0系统的手机上时,但在运行时调用上面不可用的 API 就会崩溃报错:Method NotFound。
对于 Google play 和 国内的应用市场来说,它们会根据 minSdkVersion 的值来决定开发者上传的应用(APK包)对安装了不同系统版本的终端用户是否可见。
在开发时 minSdkVersion 也可以帮助你:如果你调用的 API(由 CompileSdkVersion 值决定),在 minSdkVersion 指代的 SDK 里并不存在的话,IDE 会提示错误,举个例子:
假如我们项目里,compileSdkVersion 大于或等于23(Android 6.0 需动态申请特殊权限),minSdkVersion等于21,如果如果在代码里调用 minSdkVersion 中并不存在的 API 方法:checkSelfPermission(""), 就会出现代码错误提示,把光标停留在这个方法上面,就会提示如下错误:
Call requires API level 23 (current min is 21): android.content.ContextWrapper#checkSelfPermission
点击左侧的感叹号,会提示解决方案(如下图 - 1),这可以帮助你避免应用在运行时调用不存在的 API 的问题。注意:虽然报错,但是不影响编译和打包APK。
处理办法:
- 1.添加@RequiresApi(api = Build.VERSION_CODES.M)
- 2.添加@TargetApi(Build.VERSION_CODES.M)
- 3.添加@SuppressLint(“NewApi”)
- 4.添加运行时SDK版本判断
第一、第二、第三种方法只是能编译通过,到了低于 API23 的系统运行,将会引发 java.lang.NoSuchMethodError。正确的做法是添加运行时 SDK 版本判断,判断为低版本时添加其他方法实现该功能,使用运行时检查系统版本的方式如下:
private void checkPermission() {
//VERSION_CODES_M = 23
if(Build.VERSION.SDK_INT>=23){
checkSelfPermission("");
}else {
... do something ...
}
}
注意:你在项目中所使用的库,如 Support Library 或 Google Play services,它们可能有各自的 minSdkVersion 。你的应用设置的 minSdkVersion 必需大于等于这些库的 minSdkVersion。例如有三个库,它们的 minSdkVersion 分别是 4, 7 和 9 ,那么你的 minSdkVersion 必需至少是 9 才能使用它们。
当你决定使用什么 minSdkVersion 时候,你应该参考当前的 Android 分布统计,它显示了最近 7 天所有访问 Google Play 的设备信息,他们就是你把应用发布到 Google Play 时的潜在用户。所以这是一个「商业决策」问题,取决于为了支持额外 3% 的设备并确保最佳体验而付出的开发和测试成本是否值得,你需要在潜在的「市场份额」和「维护成本」之间做出权衡。不管怎么说,minSdkVersion
越低,就需要花费越多时间来解决旧的 BUG,并且可能需要 APP 里添加替代方法(高版本 SDK 已经实现的 API 在低版本还没有实现)。
CompileSdkVersion 指定 Gradle 编译你的 APP 时使用的 Android API 版本,你的 App 可以使用该版本或者更低版本的 API 特性。简单来说,如果你的 APP 在开发中需要用到一些比较新的 API 特性,那么你的 compileSdkVersion 的数值就应该大于或等于相应的 API 级别。
需要强调的是 compileSdkVersion 值不会被打包到 APK 中(而 minSdkVersin 和 targetSdkVersion 会),它只是在编译的时候起作用,所以 CompileSdkVersion 不会决定 APP 运行时的行为。当你修改了 compileSdkVersion 的时候,可能会出现新的编译警告、编译错误。
注意:如果使用 Support Library ,那么使用最新发布的 Support Library 就需要使用最新的 SDK 编译。例如,要使用 23.1.1 版本的 Support Library ,compileSdkVersion 就必需至少是 23 (大版本号要一致!)。通常,新版的 Support Library 随着新的系统版本而发布,它为系统新增加的 API 和新特性提供兼容性支持。
在现有代码上使用新的编译检查的好处:这个属性设置的越大,废弃的方法就越多,这样可以帮助我们适当处理已经弃用的 API ,并且为使用新的 API 做好准备,所以在开发过程中,推荐总是使用最新的 SDK 进行编译。
疑问:compileSdkVersion让我们的 APP 在编译期间可以调用的最新 API,但如果 APP 在更低版本的系统运行时没有找到对应的 API 肯定会崩溃,那么如何实现 Android 高版本 API 方法在低版本系统上的兼容性呢?
参考:MinSdkVersion - 2.2.2节 API可用性/兼容性检测。这其实是由 CompileSdkVersion 和 MinSdkVersion 两者共同决定的。
你可以放心地将 CompileSdkVersion 的值设置为最新的 API LEVEL 值,只要在 IDE(如:Android studio - programs 窗口)出现错误提示时(如图-2) 做了正确的处理,就可以很好的兼容低版本系统,同时又能在高版本系统应用最新的 API 而不是已经废弃的 API。
对于新应用,请选择可用的最新版本。对于现有应用,请在方便时将其更新到最新版本。
TargetSdkVersion 按照字面意思理解就是目标 SdkVersion。如果没有设置,则默认值为minSdkVersion。TargetSdkVersion 是 Android 系统实现「前向兼容」的主要手段,当你设置了targetSdkVersion的时候,表示你已经充分测试过了你的App在该目标版本的运行情况(准确的说应该是从 minSdkVersion 到 targetSdkVersion 区间所有的系统版本),除非更新 targetSdkVersion,否则不改变应用的行为。这句话不太好理解,看完后面对其“作用”的分析就会明白了。
TargetSdkVersion 是 Android 系统提供前向兼容的主要手段。这是什么意思呢?假如用户安装了APP,但是用户的 Android 系统会不断升级,对应同一个 API(方法),内部的实现逻辑已经发生了改变,新逻辑可能会影响之前调用此 API 的 APP,为了兼容此问题,引入 targetSdkVersion。当 targetSdkVersion >= API LEVEL(某个系统版本)时再生效新的逻辑,否则还是沿用之前的逻辑。所以从这个角度来说,如果你修改通常是调高 targetSdkVersion 的数值,就一定要做充分的测试后才能发布,因为调用的 API 虽然没变,但是内部逻辑已经发送了改变。
打个比方:
APP 客户端调用服务端提供的 API 接口,通常我们请求参数会带上客户端的版本号(相当于 targetSdkVersion ),当后台的 API 方法的逻辑发生变化时(相当于 Android 系统版本升级),如果返回的数据内容和格式有变化(行为发生改变),我们的 API 接口也必须要兼容老版本的客户端APP,通常的做法是在服务端根据客户端的版本号做判断,返回不同的数据给不同版本的客户端。
现在旧版本的APP将版本号升高到最新版本号(相当于最新的API LEVEL ),那么获取到API返回数据也不相同了,这就需要对新的改变测试无误后再重新发布,如此周而复始。
例一:
在 Android 4.4 (API 19)以后,AlarmManager 的 set() 和 setRepeat() 这两个 API 的行为发生了变化。
- 在 Android 4.4 以前,这两个 API 设置的都是精确的时间,系统能保证在 API 设置的时间点上唤醒 Alarm。
- 在Android 4.4,因为省电原因实现了 AlarmManager 的对齐唤醒,这两个 API 设置唤醒的时间,系统都对待成不精确的时间,系统只能保证在你设置的时间点之后某个时间唤醒。
Android 4.4 上 AlarmManger 的一部分源代码:
private final boolean mAlwaysExact;
AlarmManager(IAlarmManager service, Context ctx) {
mService = service;
final int sdkVersion = ctx.getApplicationInfo().targetSdkVersion;
mAlwaysExact = (sdkVersion < Build.VERSION_CODES.KITKAT);
}
APK 在调用系统 AlarmManager 的 set()
或者 setRepeat()
的时候,系统首先会查一下调用的 APK 的 targetSdkVersion 信息,如果小于 19,就还是按照老的行为,即精确设置唤醒时间,否者执行新的行为。
例二:
在Andorid 6.0系统( API = 23)上运行的 APP,对一些特殊 API 的调用,需要运行中动态申请权限。如果你 APP 的 targetSdkVersion 的值小于或等于22,那么即使升级到6.0以上,你的应用不申请权限仍然可以正常运行。所以 Google Play 和国内的应用市场,通常会强制要求提交应用的 targetSdkVersion 值必须要大于22。为了能正常通过审核,就必须修改 targetSdkVersion,但要注意: targetSdkVersion修改后需要进行测试,因为改变此值后,会引发新系统特性,例如运行时权限申请等。
对于新应用,请选择可用的最新版本。对于现有应用,请在方便时将其更新到最新版本。
其实这三个属性的理解之所以有些困难,是因为我们没有站在 Android 系统 API 设计的角度来理解,如果你要处理系统版本的兼容性,那么就你必须要考虑以下几个问题:
- 你的应用最低适配到什么系统版本? - minSdkVersion
- 高版本的 API(不同方法:高版本有,低版本没有),如何在低版本的系统正常运行? - minSdkVersion 与 compileSdkVersion
- 系统版本升级后(相同方法,逻辑发生改变),如何保证已经发布的应用仍然和之前验证过的行为保持一致?- targetSdkVersion
当你主动去思考这些问题,你大概率也会自己设计出类似的三个属性来实现我们的需求,这样理解是不是就更容易了。
回到开头,我们新建项目时,compileSdkVersion 和 targetSdkVersion 默认值是 Google 推荐的最新 API Level,但是 minSdkVersion 由我们自己选择,综合分析,它们通常满足下面的关系:
minSdkVersion(lowest possible) <= targetSdkVersion <= compileSdkVersion(latest SDK)
设想一下,我们现在新建一个 Android 项目,targetSdkVersion 与 compileSdkVersion 都使用当前最新的 SDK API LEVEL,比如32,但是过了一段时间后,官方新的API升级到了35,通常的做法是经过评估后(看看新的API LEVEL有哪些亮点,对现有的项目会产生什么影响),然后在项目开发周期的某个时间,将 compileSdkVersion 和 targetSdkVersion 数值修改到和最新的 API LEVEL 一样大,经过严格测试并保证 APP 对 API 的逻辑变化做了相应的适配后才会正式发布 APP 的新版本。
也就是说:用较低的 minSdkVersion 来覆盖最大的人群,用最新的 SDK 设置 targetSdkVersion 和 compileVersion 来获得最好的外观和行为。
官方文档参考:
1. https://developer.android.com/ndk/guides/sdk-versions
2. https://developer.android.com/guide/topics/manifest/uses-sdk-element