为不同的 API 级别创建多个 APK

如果您将应用发布到 Google Play,则应构建并上传Android 应用包。这样做时,Google Play 会自动为每个用户的设备配置生成和提供优化的 APK,因此他们只需下载运行您的应用所需的代码和资源。如果您没有发布到 Google Play,则发布多个 APK 很有用,但是您必须自己构建、签名和管理每个 APK。

在开发您的 Android 应用以利用 Google Play 上的多个 APK 时,务必从一开始就采用一些良好的实践,并防止在开发过程的后期出现不必要的麻烦。本课程将向您展示如何创建应用的多个 APK,每个 APK 都涵盖略微不同的 API 级别范围。您还将获得一些必要的工具,使维护多个 APK 代码库尽可能轻松。

确认您需要多个 APK

当您尝试创建一个可在多代 Android 平台上运行的应用程序时,自然希望您的应用程序能够利用新设备上的新功能,而不会牺牲向后兼容性。乍一看,多个 APK 支持似乎是最佳解决方案,但这通常并非如此。多个 APK 开发者指南的使用单个 APK 代替部分包含一些关于如何使用单个 APK 实现此目标的有用信息,包括我们支持库的使用。您还可以学习如何编写仅在单个 APK 中的特定 API 级别运行的代码,而无需采用计算成本高昂的技术,例如这篇文章中的反射。

如果您可以管理它,将您的应用程序限制在一个 APK 中具有以下几个优点:

  • 发布和测试更容易
  • 只有一个代码库需要维护
  • 您的应用程序可以适应设备配置更改
  • 跨设备的应用恢复正常工作
  • 您无需担心市场偏好、“升级”从一个 APK 到下一个 APK 的行为,或者哪个 APK 与哪一类设备配合使用

本课程的其余部分假设您已经研究了该主题,认真吸收了链接资源中的材料,并确定多个 APK 是您的应用程序的正确路径。

规划您的需求

首先创建一个简单的图表,以快速确定您需要多少个 APK,以及每个 APK 涵盖的 API 范围。为了方便参考,Android 开发者网站的平台版本页面提供了关于运行给定版本的 Android 平台的活动设备相对数量的数据。此外,尽管一开始听起来很简单,但跟踪每个 APK 将要定位的 API 级别集很快就会变得很困难,特别是如果存在一些重叠(通常存在)。幸运的是,很容易快速、轻松地规划您的需求,并为以后提供简单的参考。

为了创建您的多个 APK 图表,首先使用一行单元格来表示 Android 平台的各种 API 级别。在末尾添加一个额外的单元格以表示未来版本的 Android。

3 4 5 6 7 8 9 10 11 12 13 +

现在只需将图表着色,以便每种颜色代表一个 APK。这是一个如何将每个 APK 应用于特定 API 级别范围的示例。

3 4 5 6 7 8 9 10 11 12 13 +

创建此图表后,将其分发给您的团队。您的项目的团队沟通立即变得更简单了,因为不必询问“API 级别 3 到 6 的 APK,呃,你知道的,Android 1.x 的那个。进展如何?”您可以简单地说“蓝色 APK 的进展如何?”

将所有公共代码和资源放在库项目中

无论您是修改现有的 Android 应用程序还是从头开始创建一个应用程序,这都是您应该对代码库做的第一件事,也是最重要的一件事。库项目中的所有内容只需要更新一次(例如语言本地化字符串、颜色主题、共享代码中修复的错误),这可以提高您的开发时间并降低可能很容易避免的错误的可能性。

注意:虽然创建和包含库项目的实现细节不在本课程的讨论范围之内,但您可以通过阅读创建 Android 库来快速入门。

如果您要将现有应用程序转换为使用多个 APK 支持,请仔细检查您的代码库中每个本地化字符串文件、值列表、主题颜色、菜单图标和布局,这些内容不会跨 APK 更改,并将它们全部放在库项目中。不会发生太大变化的代码也应该放在库项目中。您可能会发现自己扩展这些类以从一个 APK 添加一两个方法到另一个 APK。

另一方面,如果您是从头开始创建应用程序,请尽可能先在库项目中编写代码,只有在必要时才将其移至单个 APK。从长远来看,这比将其添加到一个 APK,然后另一个,再另一个,几个月后又试图弄清楚这个代码块是否可以移回库部分而不会弄坏任何东西要容易得多。

创建新的 APK 项目

您要发布的每个 APK 都应该有一个单独的 Android 项目。为了方便组织,请将库项目和所有相关的 APK 项目放在同一个父文件夹下。还要记住,每个 APK 都需要具有相同的包名,尽管它们不需要与库共享包名。如果您要按照前面描述的方案创建 3 个 APK,则根目录可能如下所示

alexlucas:~/code/multi-apks-root$ ls
foo-blue
foo-green
foo-lib
foo-red

创建项目后,将库项目作为引用添加到每个 APK 项目。如果可能,请在库项目中定义您的起始 Activity,并在您的 APK 项目中扩展该 Activity。在库项目中定义起始 Activity 使您有机会将所有应用程序初始化放在一个地方,这样每个单独的 APK 不必重新实现“通用”任务,例如初始化分析、运行许可证检查以及任何其他不会从一个 APK 更改到另一个 APK 的初始化过程。

调整清单文件

当用户通过 Google Play 下载使用多个 APK 的应用程序时,将使用两个简单的规则选择要使用的正确 APK

  • 清单文件必须显示该特定 APK 符合条件
  • 在符合条件的 APK 中,版本号最高的获胜

例如,让我们采用前面描述的多个 APK 集,并假设我们没有为任何 APK 设置最大 API 级别。单独来看,每个 APK 的可能范围如下所示

3 4 5 6 7 8 9 10 11 12 13 +
3 4 5 6 7 8 9 10 11 12 13 +
3 4 5 6 7 8 9 10 11 12 13 +

由于需要具有更高 minSdkVersion 的 APK 也具有更高的版本代码,因此我们知道就 versionCode 值而言,红色 ≥ 绿色 ≥ 蓝色。因此,我们可以有效地将图表折叠起来,如下所示

3 4 5 6 7 8 9 10 11 12 13 +

现在,让我们进一步假设红色 APK 有一些其他两个 APK 没有的要求。Android 开发者指南的Google Play 过滤器页面列出了所有可能的因素。例如,让我们假设红色需要前置摄像头。事实上,红色 APK 的全部意义在于将前置摄像头与 API 11 中添加的全新功能结合起来。但是,事实证明,并非所有支持 API 11 的设备都具有前置摄像头!太可怕了!

幸运的是,如果用户从这样的设备浏览 Google Play,Google Play 将查看清单文件,看到红色将前置摄像头列为要求,并将其静默忽略,因为它已确定红色和该设备并非天作之合。然后,它将看到绿色不仅与具有 API 11 的设备向前兼容(因为未定义 maxSdkVersion),而且也不在乎是否有前置摄像头!用户仍然可以从 Google Play 下载该应用程序,因为尽管发生了前置摄像头问题,但仍然有一个 APK 支持该特定 API 级别。

为了使所有 APK 保持在单独的“轨道”上,拥有良好的版本代码方案非常重要。推荐的方案可以在我们的开发者指南的版本代码部分找到。由于 APK 示例集只处理三个可能维度中的一个,因此只需将每个 APK 分隔 1000,将前几位数字设置为该特定 APK 的 minSdkVersion,然后递增即可。这可能看起来像这样

蓝色:03001、03002、03003、03004...
绿色:07001、07002、07003、07004...
红色:11001、11002、11003、11004...

综上所述,您的 Android 清单文件可能如下所示

蓝色

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="03001" android:versionName="1.0" package="com.example.foo">
    <uses-sdk android:minSdkVersion="3" />
    ...

绿色

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="07001" android:versionName="1.0" package="com.example.foo">
    <uses-sdk android:minSdkVersion="7" />
    ...

红色

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:versionCode="11001" android:versionName="1.0" package="com.example.foo">
    <uses-sdk android:minSdkVersion="11" />
    ...

查看您的预发布清单

上传到 Google Play 之前,请仔细检查以下项目。请记住,这些项目与多个 APK 特别相关,绝不代表上传到 Google Play 的所有应用程序的完整清单。

  • 所有 APK 必须具有相同的包名
  • 所有 APK 必须使用相同的证书签名
  • 如果 APK 在平台版本方面存在重叠,则 minSdkVersion 更高的 APK 必须具有更高的版本代码
  • 仔细检查您的清单过滤器中是否存在冲突信息(仅支持杯形蛋糕的 XLARGE 屏幕的 APK 将不会被任何人看到)
  • 每个 APK 的清单文件必须至少在一个支持的屏幕、OpenGL 纹理或平台版本方面是唯一的
  • 尝试在至少一台设备上测试每个 APK。如果没有,您的开发机器上就有一个业内最可定制的设备模拟器。尽情使用吧!

在推向市场之前检查已编译的 APK 也很有价值,以确保不会出现任何意外情况,这些意外情况可能会隐藏您在 Google Play 上的应用程序。使用“aapt”工具实际上非常简单。Aapt(Android 资源打包工具)是创建和打包 Android 应用程序的构建过程的一部分,也是一个非常方便的检查工具。

>aapt dump badging
package: name='com.example.hello' versionCode='1' versionName='1.0'
sdkVersion:'11'
uses-permission:'android.permission.SEND_SMS'
application-label:'Hello'
application-icon-120:'res/drawable-ldpi/icon.png'
application-icon-160:'res/drawable-mdpi/icon.png'
application-icon-240:'res/drawable-hdpi/icon.png'
application: label='Hello' icon='res/drawable-mdpi/icon.png'
launchable-activity: name='com.example.hello.HelloActivity'  label='Hello' icon=''
uses-feature:'android.hardware.telephony'
uses-feature:'android.hardware.touchscreen'
main
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--'
densities: '120' '160' '240'

检查 aapt 输出时,请务必检查您的 supports-screens 和 compatible-screens 是否没有冲突的值,并且您没有因在清单文件中设置的权限而添加意外的“uses-feature”值。在上面的示例中,APK 将不会对很多设备可见。

为什么?通过添加所需的权限 SEND_SMS,隐式添加了 android.hardware.telephony 的功能要求。由于 API 11 是 Honeycomb(专门针对平板电脑优化的 Android 版本),并且没有 Honeycomb 设备具有电话硬件,因此 Google Play 将在所有情况下过滤掉此 APK,直到出现 API 级别更高且具有电话硬件的未来设备。

幸运的是,这可以通过向您的清单文件添加以下内容轻松解决

<uses-feature android:name="android.hardware.telephony" android:required="false" />

还会隐式添加android.hardware.touchscreen要求。如果希望您的 APK 在非触摸屏设备(例如电视)上可见,则应向清单文件添加以下内容

<uses-feature android:name="android.hardware.touchscreen" android:required="false" />

完成预发布清单后,将您的 APK 上传到 Google Play。应用程序在 Google Play 中浏览时可能需要一些时间才能显示出来,但显示出来后,请执行最后一次检查。将应用程序下载到您可能拥有的任何测试设备上,以确保 APK 正在面向目标设备。恭喜您,完成啦!