如果您要将应用发布到 Google Play,应构建并上传 Android App Bundle。这样一来,Google Play 会自动为每位用户的设备配置生成并提供优化的 APK,以便用户仅下载运行您的应用所需的代码和资源。如果您不发布到 Google Play,发布多个 APK 会很有用,但您必须自行构建、签名和管理每个 APK。
在开发利用 Google Play 上多个 APK 的 Android 应用时,从一开始就采用一些良好做法非常重要,以防止在后续开发过程中出现不必要的麻烦。本课将向您展示如何创建应用的多个 APK,每个 APK 涵盖不同类别的屏幕尺寸。您还将获得一些必要的工具,以使维护多 APK 代码库尽可能轻松。
确认您是否需要多个 APK
在尝试创建适用于各种 Android 设备的应用程序时,您自然希望您的应用程序在每个设备上都呈现最佳效果。您希望利用大屏幕的空间,但仍能在小屏幕上运行;您希望使用前沿设备上提供的新 Android API 功能或视觉纹理,但又不放弃旧设备。乍一看,多 APK 支持似乎是最佳解决方案,但通常情况并非如此。多 APK 指南的改为使用单个 APK 部分包含一些有用的信息,说明如何通过单个 APK 实现所有这些功能,包括使用我们的支持库,以及指向整个 Android 开发者指南中资源的链接。
如果可以,将应用程序限制为单个 APK 具有以下几个优点,包括
- 发布和测试更简单
- 只需维护一个代码库
- 您的应用程序可以适应设备配置更改
- 跨设备应用恢复可直接运行
- 您无需担心市场偏好、从一个 APK “升级”到下一个 APK 的行为,或者哪个 APK 适用于哪类设备
本课的其余部分假设您已研究过该主题,认真吸收了所链接资源中的材料,并确定多个 APK 是您应用程序的正确路径。
绘制您的要求图表
首先创建一个简单图表,快速确定您需要多少个 APK,以及每个 APK 涵盖的屏幕尺寸。幸运的是,您可以轻松快速地绘制出您的要求,并将其作为日后参考。假设您想将 APK 拆分到两个维度:API 和屏幕尺寸。创建一个表格,为每对可能的值创建一行和一列,并用颜色填充一些“块”,每种颜色代表一个 APK。
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | + | |
小 | |||||||||||
普通 | |||||||||||
大 | |||||||||||
特大 |
上面是一个包含四个 APK 的示例。蓝色适用于所有小/普通屏幕设备,绿色适用于大屏幕设备,红色适用于特大屏幕设备,它们的 API 范围都是 3-10。紫色是一个特殊情况,它适用于所有屏幕尺寸,但仅适用于 API 11 及更高版本。更重要的是,只需扫一眼这个图表,您就能立即知道哪个 APK 覆盖了任何给定的 API/屏幕尺寸组合。此外,您还可以为每个 APK 设置时髦的代号,因为问您的同事“我们测试过红色的吗?”比问“我们测试过针对 Xoom 的 3 到 10 级特大 APK 吗?”要简单得多。把这张图表打印出来,交给每个在您的代码库上工作的人。生活会轻松很多。
将所有通用代码和资源放入库项目中
无论您是修改现有的 Android 应用程序还是从头开始创建,这都是您应该对代码库做的第一件事,也是迄今为止最重要的一件事。所有放入库项目中的内容只需更新一次(例如语言本地化字符串、颜色主题、共享代码中修复的错误),这可以缩短您的开发时间,并减少本可轻易避免的错误发生的可能性。
注意:虽然如何创建和包含库项目的实现细节超出了本课的范围,但您可以通过阅读创建 Android 库来快速掌握。
如果您正在将现有应用程序转换为使用多 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-purple foo-red
创建项目后,将库项目作为引用添加到每个 APK 项目中。如果可能,在库项目中定义您的起始 Activity,并在您的 APK 项目中扩展该 Activity。在库项目中定义起始 Activity,可以让您有机会将所有应用程序初始化放在一个地方,这样每个单独的 APK 就无需重新实现“通用”任务,例如初始化 Analytics、运行许可检查以及任何其他在不同 APK 之间变化不多的初始化过程。
调整清单
当用户通过 Google Play 下载使用多个 APK 的应用程序时,将根据两条简单规则选择要使用的正确 APK
- 清单必须显示该特定 APK 符合条件
- 在符合条件的 APK 中,版本号最高的获胜。
举例来说,我们采用前面描述的多个 APK 集,并假设每个 APK 都已设置为支持所有大于其“目标”屏幕尺寸的屏幕尺寸。我们来看看前面的示例图表
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | + | |
小 | |||||||||||
普通 | |||||||||||
大 | |||||||||||
特大 |
由于覆盖范围可以重叠,我们可以这样描述每个 APK 覆盖的区域
- 蓝色覆盖所有屏幕,minSDK 3。
- 绿色覆盖大屏幕及更高版本,minSDK 3。
- 红色覆盖特大屏幕(通常是平板电脑),minSDK 9。
- 紫色覆盖所有屏幕,minSDK 11。
请注意,这些规则中存在大量重叠。例如,一个 API 11 的特大设备可以运行指定的 4 个 APK 中的任何一个。但是,通过使用“最高版本号获胜”规则,我们可以设置偏好顺序如下
紫色 ≥ 红色 ≥ 绿色 ≥ 蓝色
为什么要允许所有这些重叠?我们假设紫色 APK 有一些其他两个 APK 没有的要求。Android 开发者指南的Google Play 上的过滤器页面列出了所有可能的“罪魁祸首”。举例来说,我们假设紫色 APK 需要前置摄像头。事实上,紫色 APK 的全部意义在于使用前置摄像头进行有趣的活动!但事实证明,并非所有 API 11+ 设备都拥有前置摄像头!太可怕了!
幸运的是,如果用户从此类设备浏览 Google Play,Google Play 会查看清单,发现紫色 APK 列出了前置摄像头作为要求,然后悄悄忽略它,因为它已确定紫色 APK 和该设备并非天作之合。然后它会发现红色 APK 不仅与特大设备兼容,而且根本不关心是否有前置摄像头!用户仍然可以从 Google Play 下载该应用,因为尽管发生了前置摄像头的小意外,但仍然有一个 APK 支持该特定 API 级别。
为了使您的所有 APK 保持在独立的“轨道”上,拥有一个良好的版本代码方案非常重要。推荐的方案可以在我们开发者指南的版本代码部分找到。值得阅读整个部分,但基本要点是,对于这组 APK,我们使用两位数字表示 minSDK,两位数字表示最小/最大屏幕尺寸,三位数字表示构建号。这样,当设备升级到新版本的 Android(例如,从 10 升级到 11)时,任何现在符合条件且优于当前已安装 APK 的 APK 都将被设备视为“升级”。版本号方案应用于示例 APK 集时,可能如下所示
蓝色:0304001、0304002、0304003...
绿色:0334001、0334002、0334003
红色:0344001、0344002、0344003...
紫色:1104001、1104002、1104003...
综合来看,您的 Android 清单可能如下所示
蓝色
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="0304001" android:versionName="1.0" package="com.example.foo"> <uses-sdk android:minSdkVersion="3" /> <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" /> ...
绿色
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="0334001" android:versionName="1.0" package="com.example.foo"> <uses-sdk android:minSdkVersion="3" /> <supports-screens android:smallScreens="false" android:normalScreens="false" android:largeScreens="true" android:xlargeScreens="true" /> ...
红色
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="0344001" android:versionName="1.0" package="com.example.foo"> <uses-sdk android:minSdkVersion="3" /> <supports-screens android:smallScreens="false" android:normalScreens="false" android:largeScreens="false" android:xlargeScreens="true" /> ...
紫色
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1104001" android:versionName="1.0" package="com.example.foo"> <uses-sdk android:minSdkVersion="11" /> <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" /> ...
请注意,从技术上讲,多个 APK 既可以使用 supports-screens 标签,也可以使用 compatible-screens 标签。Supports-screens 通常是首选,而同时使用两者通常是一个非常糟糕的主意——这会使事情不必要地复杂化,并增加出错的机会。另请注意,清单明确设置了每个屏幕尺寸的值,而不是利用默认值(small 和 normal 默认始终为 true)。这可以省去您日后的麻烦——例如,target SDK 小于 9 的清单将自动把 xlarge 设置为 false,因为该尺寸当时尚不存在。因此请明确指定!
检查您的发布前核对清单
在上传到 Google Play 之前,请仔细检查以下各项。请记住,这些内容专门与多个 APK 相关,绝不代表所有上传到 Google Play 的应用程序的完整核对清单。
- 所有 APK 都必须具有相同的软件包名称。
- 所有 APK 都必须使用相同的证书签名。
- 如果 APK 在平台版本上重叠,则 minSdkVersion 较高的 APK 必须具有较高的版本代码。
- 您希望您的 APK 支持的每个屏幕尺寸,请在清单中设置为 true。您希望避免的每个屏幕尺寸,请设置为 false。
- 仔细检查您的清单过滤器是否存在冲突信息(只支持 XLARGE 屏幕上的 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: 'xlarge' supports-any-density: 'true' locales: '--_--' densities: '120' '160' '240'
当您检查 aapt 输出时,请务必检查 supports-screens 和 compatible-screens 是否没有冲突值,并且是否没有因为您在清单中设置的权限而意外添加了“uses-feature”值。在上面的示例中,该 APK 对大多数(如果不是所有)设备都将不可见。
为什么?通过添加所需的权限 SEND_SMS,隐式添加了 android.hardware.telephony 的功能要求。由于大多数(如果不是所有)特大设备都是没有电话硬件的平板电脑,因此 Google Play 会在这些情况下过滤掉此 APK,直到未来的设备既足够大可以报告为特大屏幕尺寸,又拥有电话硬件。
幸运的是,通过在您的清单中添加以下内容可以轻松修复此问题
<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 正在定位预期的设备。恭喜,您已完成!