Android targetSdkVersion 原理

澳门新葡亰网站注册 1

前几天 Google 官方发布文章解析 compileSdkVersion、minSdkVersion 以及
targetSdkVersion 的含义,以及合理设置各个值的意义,原文 Picking your
compileSdkVersion, minSdkVersion, and
targetSdkVersion(后面简称
“原文”),还有翻译版。

参考:
Android targetSdkVersion
原理
如何选择 compileSdkVersion, minSdkVersion 和
targetSdkVersion
Google 官方文章:Picking your compileSdkVersion, minSdkVersion, and
targetSdkVersion
Android
Develop

其中,compileSdkVersion 和 minSdkVersion 都非常好理解,前者表示编译的
SDK 版本,后者表示应用兼容的最低 SDK 版本。但是对于 targetSdkVersion
其实很难一句话解析清楚,原文用了“万能”的词
—— interesting 来描述。以前我也有一些迷糊,看到有些人和我有同样的困惑,本文试图彻底解决这个问题。

Google 官方发布文章解析 compileSdkVersion、minSdkVersion 以及
targetSdkVersion 的含义,以及合理设置各个值的意义,原文 Picking your
compileSdkVersion, minSdkVersion, and
targetSdkVersion

原文是这么说的:

compileSdkVersion

compileSdkVersion 告诉 Gradle 用哪个 Android SDK
版本编译你的应用。它纯粹只是在编译的时候使用。当你修改了
compileSdkVersion
的时候,可能会出现新的编译警告、编译错误等(你真的应该修复这些警告,他们的出现一定是有原因的)。需要强调的是修改
compileSdkVersion 不会改变运行时的行为,compileSdkVersion
并不会打包进APK里只是在编译时使用

因此我们强烈推荐总是使用最新的 SDK
进行编译
。在自己的代码上使用最新SDK的编译检查(lint?)可以获得很多好处,可以避免使用最新弃用的
API ,并且为使用新的 API 做好准备。

注意,如果使用 Support
Library
,那么使用最新发布的 Support Library 就需要使用最新的 SDK
编译。通常,新版的 Support Library
随着新的系统版本而发布,它为系统新增加的 API
和新特性提供兼容性支持。例如,要使用 23.1.1 版本的 Support Library
,compileSdkVersion 就必需至少是 23 (大版本号要一致!)。

targetSdkVersion is the main way Android provides forward
compatibility

minSdkVersion

minSdkVersion 是应用可以运行的最低版本要求。minSdkVersion 是 Google
Play 商店用来判断用户设备是否可以安装某个应用的标志之一。

在开发时 minSdkVersion
也起到一个重要角色:lint
默认会在项目中运行,它在你使用了高于 minSdkVersion 的 API
时会警告你,帮你避免调用不存在的 API
的运行时问题。如果只在较高版本的系统上才使用某些
API,通常使用澳门新葡亰网站注册,运行时检查系统版本的方式解决。

请记住,你所使用的库可能有他们自己的 minSdkVersion 。你的应用设置的
minSdkVersion 必需大于等于这些库的 minSdkVersion 。例如有三个库,它们的
minSdkVersion 分别是 4, 7 和 9 ,那么你的 minSdkVersion 必需至少是 9
才能使用它们。在少数情况下,你仍然想用一个比你应用的 minSdkVersion
还高的库(处理所有的边缘情况,确保它只在较新的平台上使用),你可以使用
tools:overrideLibrary
标记,但请做彻底的测试!

targetSdkVersion 是 Android
系统提供前向兼容的主要手段。这是什么意思呢?随着 Android
系统的升级,某个系统的 API 或者模块的行为可能会发生改变,但是为了保证老
APK 的行为还是和以前兼容。只要 APK 的 targetSdkVersion 不变,即使这个
APK 安装在新 Android
系统上,其行为还是保持老的系统上的行为,这样就保证了系统对老应用的前向兼容性。

targetSdkVersion

compileSdkVersion 和 minSdkVersion 都非常好理解,前者表示编译的 SDK
版本,后者表示应用兼容的最低 SDK 版本。但是对于 targetSdkVersion
其实很难一句话解析清楚,原文:The most interesting of the three,
however, is targetSdkVersion.
。以前我也有一些迷糊,看到有些人和我有同样的困惑,本文试图彻底解决这个问题。

原文:targetSdkVersion is the main way Android provides forward
compatibility

targetSdkVersion 是 Android
系统提供前向兼容的主要手段。这是什么意思呢?随着 Android
系统的升级,某个 API 或者模块的行为可能会发生改变,但是为了保证APK
的行为还是和以前一致。只要 APK 的 targetSdkVersion 不变,即使这个 APK
安装在新 Android
系统上,其行为还是保持老的系统上的行为,这样就保证了系统对老应用的前向兼容性。

这里还是用原文的例子,在 Android 4.4 (API 19)以后,AlarmManager 的
set()
和setRepeat()
这两个 API 的行为发生了变化。在 Android 4.4 以前,这两个 API
设置的都是精确的时间,系统能保证在 API 设置的时间点上唤醒
Alarm。因为省电原因 Android 4.4 系统实现了 AlarmManager
的对齐唤醒,这两个 API
设置唤醒的时间,系统都对待成不精确的时间,系统只能保证在你设置的时间点之后某个时间唤醒。

这时,虽然 API 没有任何变化,但是实际上 API 的行为却发生了变化,如果 APK
中使用了此 API,并且在应用中的行为非常依赖 AlarmManager
在精确的时间唤醒,例如闹钟应用。如果 Android 系统不能保证兼容,老的 APK
安装在新系统手机上,就会出现问题。

Android 系统是怎么保证这种兼容性的呢?这时候 targetSdkVersion
就起作用了。APK 在调用系统 AlarmManager 的 set() 或者 setRepeat()
的时候,系统首先会查一下调用的 APK 的 targetSdkVersion 信息,如果小于
19,就还是按照老的行为,即精确设置唤醒时间,否者执行新的行为。
我们来看一下 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);
}

看到这里,首选获取应用的 targetSdkVersion,判断是否是小于
Build.VERSION_CODES.KITKAT (即 API Level 19),来设置 mAlwaysExact
变量,表示是否使用精确时间模式。

public static final long WINDOW_EXACT = 0;  
public static final long WINDOW_HEURISTIC = -1;
private long legacyExactLength() {  
    return (mAlwaysExact ? WINDOW_EXACT : WINDOW_HEURISTIC);
}
public void set(int type, long triggerAtMillis, PendingIntent operation) {  
    setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null);
}

这里看到,直接影响到 set() 方法给 setImpl() 传入不同的参数,从而影响到了
set() 的执行行为。具体的实现在
AlarmManagerService.java,这里就不往下深究了。
看到这里,发现其实 Android 的 targetSdkVersion
并没有什么特别的,系统使用它也非常直接,甚至很“粗糙”。仅仅是用过下面的
API 来获取
targetSdkVersion,来判断是否执行哪种行为:getApplicationInfo().targetSdkVersion;

所以,我们可以猜测到,如果 Android
系统升级,发生这种兼容行为的变化时,一般都会保存新旧两种逻辑,并通过
if-else 方法来判断执行哪种逻辑。果然,在源码中搜索,我们会发现不少类似
getApplicationInfo().targetSdkVersion < Buid.XXXX
这样的代码,相对于浩瀚的 Android
源码量来说,这些还是相对较少了。其实原则上,这种会导致兼容性问题的修改还是越少越好,所以每次发布新的
Android 版本的时候,Android
开发者网站都会列出做了哪些改变,在这里,开发者需要特别注意。

最后,我们也可以理解原文中说的那句话的含义,明白了为什么修改了 APK 的
targetSdkVersion 行为会发生变化,也明白了为什么修改 targetSdkVersion
需要做完整的测试了。

这里还是用原文的例子,在 Android 4.4 (API 19)以后,AlarmManager
的 set() 和 setRepeat() 这两个
API 的行为发生了变化。在 Android 4.4 以前,这两个 API
设置的都是精确的时间,系统能保证在 API 设置的时间点上唤醒
Alarm。因为省电原因 Android 4.4 系统实现了 AlarmManager
的对齐唤醒,这两个 API
设置唤醒的时间,系统都对待成不精确的时间,系统只能保证在你设置的时间点之后某个时间唤醒。

Gradle和SDK版本

所以设置正确的 compileSdkVersion, minSdkVersion 和 targetSdkVersion
很重要。

澳门新葡亰网站注册 1

build.gradle设置

所以编译时用到的 compileSdkVersion
是和其他构建设置放在一起的(如上图的: buildToolsVersion)作为Android
的设置。其他两个稍有不同,他们在构建变体(build
variant)的那里声明。defaultConfig
是所有构建变体的基础,也是设置这些默认值的地方。
你可以想象在一个更复杂的系统中,应用的某些版本可能会有不同的
minSdkVersion 。
minSdkVersion 和 targetSdkVersion 会被打包进最终的 APK
文件中
,如果你查看生成的 AndroidManifest.xml
文件,你会看到类似下面这样的标签:

<uses-sdk android:targetSdkVersion="23" android:minSdkVersion="7" />

如果你在 manifest 文件中手工设置,你会发现 Gradle
在构建时会忽略它们
(尽管其它构建系统如eclipse、ant可能会依赖它们)。

综合来看如果你按照上面示例那样配置,你会发现这三个值的关系是:

minSdkVersion <= targetSdkVersion <= compileSdkVersion

理想上,在稳定状态下三者的关系应该更像这样:

minSdkVersion (lowest possible) <= targetSdkVersion ==
compileSdkVersion (latest SDK)

用较低的 minSdkVersion 来覆盖最大的人群,用最新的 SDK 设置 target 和
compile 来获得最好的外观和行为。

这时,虽然 API 没有任何变化,但是实际上 API 的行为却发生了变化,如果老的
APK 中使用了此 API,并且在应用中的行为非常依赖 AlarmManager
在精确的时间唤醒,例如闹钟应用。如果 Android 系统不能保证兼容,老的 APK
安装在新的系统上,就会出现问题。

附:部分英文原文

Android 系统是怎么保证这种兼容性的呢?这时候 targetSdkVersion
就起作用了。APK 在调用系统 AlarmManager
的 set() 或者 setRepeat() 的时候,系统首先会查一下调用的 APK 的
targetSdkVersion 信息,如果小于
19,就还是按照老的行为,即精确设置唤醒时间,否者执行新的行为。

compileSdkVersion

compileSdkVersion is your way to tell Gradle what version of the
Android SDK to compile your app with. Using the new Android SDK is a
requirement to use any of the new APIs added in that level.
It should be emphasized that changing your compileSdkVersion does not
change runtime behavior
. While new compiler warnings/errors may be
present when changing your compileSdkVersion, your compileSdkVersion is
not included in your APK: it is purely used at compile time. (You should
really fix those warnings though — they were added for a reason!)
Therefore it is strongly recommended that you always compile with the
latest SDK
. You’ll get all the benefits of new compilation checks on
existing code, avoid newly deprecated APIs, and be ready to use new
APIs.
Note that if you use the Support
Library,
compiling with the latest SDK is a requirement for using the latest
Support Library releases. For example, to use the 23.1.1 Support
Library, you must have a compileSdkVersion of at least 23 (those first
numbers need to match!). In general, a new version of the Support
Library is released alongside a new platform version, providing
compatibility shims to newly added APIs as well as new features.

我们来看一下 Android 4.4 上 AlarmManger 的一部分源代码:

minSdkVersion

If compileSdkVersion sets the newest APIs available to you,
minSdkVersion is the lower bound for your app. The minSdkVersion is
one of the signals the Google Play Store uses to determine which of a
user’s devices an app can be installed on.
It also plays an important role during development: by default
lint
runs against your project, warning you when you use any APIs above your
minSdkVersion, helping you avoid the runtime issue of attempting to call
an API that doesn’t exist. Checking the system version at
runtime
is a common technique when using APIs only on newer platform versions.
Keep in mind that libraries you use, such as any of the Support
Libraries
or Google Play
services,
may have their own minSdkVersion — your app’s minSdkVersion must be at
least as high as your dependencies’ minSdkVersion — if you have
libraries that require 4, 7, and 9, your minSdkVersion must be at least

  1. In rare cases where you want to continue to use a library with a
    higher minSdkVersion than your app (and deal with all edge cases/ensure
    the library is only used on newer platform versions), you can use the
    tools:overrideLibrary
    marker,
    but make sure to test thoroughly!
    When deciding on a minSdkVersion, you should consider the stats on
    the
    Dashboards,
    which give you a global look on all devices that visited the Google Play
    Store in the prior 7 days — that’s your potential audience when putting
    an app on Google Play. It is ultimately a business decision on whether
    supporting an additional 3% of devices is worth the development and
    testing time required to ensure the best experience.
    Of course, if a new API is key to your entire app, then that makes the
    minSdkVersion discussion quite a bit easier. Just remember that even
    0.7% of 1.4 billion devices is a lot of devices.