针对不同的 API 级别创建多个 APK

如果您将应用发布到 Google Play,您应构建并上传 Android App Bundle 文件。当您执行此操作时,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 1 怎么样?”,进展如何?”此类复杂的问题,您可以直接询问“蓝色 APK 进展如何?”

将所有通用代码和资源放在一个库项目中

无论您是要修改现有的 Android 应用还是从头开始创建 Android 应用,这都是您应该执行的第一项也是最重要的一项代码库操作。放入库项目中的所有内容都只需要更新一次(例如经过本地化的字符串、颜色主题背景、共享代码中修复的问题),这样可以缩短开发时间并减少发生原本很容易避免的错误的可能性。

注意:尽管有关如何创建和包含库项目的实现详情不在本课的讨论范围内,但阅读创建 Android 库有助于您跟上学习进度。

如果要将现有应用转换为支持多 APK,请搜索代码库,找出在不同 APK 之间通用的所有已本地化的字符串文件、值列表、主题背景颜色、菜单图标和布局,并将它们全部放在库项目中。不会有太大变化的代码也应该放在库项目中。您可能会发现自己需要扩展这些类,以便在 APK 之间添加一两个方法。

另一方面,如果您要从头开始创建应用,请尽可能先尝试在库项目中编写代码,然后在必要时才将其移到单个 APK 中。从长远来看,这比将 blob 添加到一个又一个又一个,然后在几个月后尝试确定是否可以将此 blob 移至库部分,而不会导致任何麻烦,这更容易管理。

创建新的 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 就不必重新实现“通用”任务,例如初始化 Google Analytics、运行许可检查,以及任何其他在 APK 之间变化不大的初始化过程。

调整清单

当用户通过 Google Play 下载使用多个 APK 的应用时,系统会使用 2 个简单的规则来选择正确的 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 有一些另外 2 种颜色的 APK 所没有的要求。“Android 开发者指南”中的 Google Play 上的过滤器页面上列出了所有可能的问题根源。举例来说,假设红色 APK 需要前置摄像头。实际上,红色 APK 的全部意义就在于将前置摄像头与 API 11 中添加的出色新功能结合起来。但事实是,有些 API 11+ 设备根本没有前置摄像头!这太糟糕了!

幸运的是,如果用户在这样的设备上浏览 Google Play,Google Play 会查看清单,看到红色 APK 列出了前置摄像头的要求并直接忽略它,因为 Google Play 已确定红色 APK 和该设备不匹配。然后会发现,绿色不仅可向前兼容采用 API 11 的设备(因为未定义 maxSdkVersion),而且不在乎是否有前置摄像头!用户仍然可以从 Google Play 下载该应用,因为尽管发生了前置摄像头的小意外,但仍然有 APK 可以支持该特定 API 级别。

为了便于区分各个 APK,您必须制定有效的版本代码方案。您可以在开发者指南中版本代码部分找到推荐的方案。由于这组示例 APK 只涉及 3 个可能维度中的一个,因此将每个 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 应具有更高的版本号
  • 仔细检查您的清单过滤器是否有冲突的信息(仅在特大屏幕上支持 Cupcake 的 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 定位到正确的设备。恭喜您大功告成了!