使用多个维度创建多个APK

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

在开发您的Android应用程序以利用Google Play上的多个APK时,务必从一开始就采用一些良好的实践,并在开发过程的后期避免不必要的麻烦。本课程将向您展示如何创建应用的多个APK,每个APK涵盖不同的屏幕尺寸类别。您还将获得一些必要的工具,以使维护多个APK代码库尽可能简单。

确认您需要多个APK

在尝试创建可在各种 Android 设备上运行的应用程序时,自然希望应用程序在每个设备上都能以最佳状态呈现。您希望利用大屏幕的空间,但仍能在小屏幕上正常工作,使用尖端设备上可用的新 Android API 功能或视觉纹理,但又不放弃旧设备。起初,多个 APK 支持似乎是最佳解决方案,但事实并非总是如此。在多个 APK 指南的使用单个 APK部分中,包含了一些有关如何使用单个 APK 完成所有这些操作的有用信息,包括使用我们的支持库,以及指向 Android 开发者指南中资源的链接。

如果可以管理,将应用程序限制为单个 APK 具有多个优势,包括

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

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

规划您的需求

首先创建一个简单的图表,快速确定您需要多少个 APK,以及每个 APK 涵盖哪些屏幕尺寸。幸运的是,您可以快速轻松地规划您的需求,并为以后提供一个简单的参考。假设您希望跨两个维度(API 和屏幕尺寸)拆分 APK。创建一个表格,为每个可能的数值对添加一行和一列,并填充一些“斑点”,每个颜色代表一个 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 项目

您要发布的每个 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 无需重新实现“通用”任务,例如初始化分析、运行许可证检查以及任何其他不会从一个 APK 更改到另一个 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 上的过滤器页面列出了所有可能的原因。为了举例说明,让我们假设紫色需要前置摄像头。事实上,紫色的全部意义在于使用前置摄像头进行娱乐!但是,事实证明,并非所有 API 11+ 设备都具有前置摄像头!太可怕了!

幸运的是,如果用户从此类设备浏览 Google Play,Google Play 将查看清单,发现紫色将前置摄像头列为要求,并将其静默忽略,因为它已确定紫色和该设备并非天作之合。然后,它将看到红色不仅与特大设备兼容,而且不关心是否存在前置摄像头!用户仍然可以从 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,并且通常最好不要同时使用两者 - 这会使事情变得不必要地复杂,并增加出错的机会。另请注意,清单明确设置了每个屏幕尺寸的值,而不是利用默认值(小屏幕和普通屏幕默认情况下始终为真)。这可以帮助您避免以后出现问题 - 例如,目标 SDK 为 < 9 的清单会将特大自动设置为 false,因为该尺寸当时还不存在。因此,请明确设置!

查看您的预发布清单

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

  • 所有 APK 必须具有相同的包名。
  • 所有 APK 必须使用相同的证书签名。
  • 如果 APK 在平台版本方面存在重叠,则 minSdkVersion 较高的 APK 必须具有较高的版本代码。
  • 您希望 APK 支持的每个屏幕尺寸,在清单中设置为 true。您希望 APK 避免的每个屏幕尺寸,设置为 false。
  • 仔细检查清单过滤器中是否存在冲突的信息(仅在特大屏幕上支持纸杯蛋糕的 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 针对的是目标设备。恭喜,您完成了!