如果您将应用发布到 Google Play,您应构建并上传 Android App Bundle 文件。当您执行此操作时,Google Play 会针对每个用户的设备配置自动生成并提供经过优化的 APK,以便他们仅下载运行您的应用所需的代码和资源。如果您不将应用发布到 Google Play,那么发布多个 APK 很有用,但您必须自行构建和管理每个 APK 并为其签名。
若要开发 Android 应用以在 Google Play 上利用多个 APK,您需要从一开始就采取一些好的做法,以免在开发过程的后期遇到不必要的麻烦。本课将向您介绍如何创建应用的多个 APK,并让其中的每个 APK 支持一个不同的 OpenGL 纹理格式子集。此外,您还将获得一些必要的工具,以便尽可能轻松地维护多 APK 代码库。
确认您需要多个 APK
尝试创建适用于所有可用 Android 设备的应用时,您自然希望应用在各种设备上都能达到最佳效果,尽管这些设备不会都支持一组相同的 GL 纹理。乍一看,支持多 APK 似乎就是最佳解决方案,但事实往往并非如此。多 APK 开发者指南的改用单个 APK 部分提供了一些有关如何使用单个 APK 实现该目标的有用信息,包括如何在运行时检测受支持的纹理格式。根据您的具体情况,可能更简单的做法是将所有格式与应用捆绑在一起,然后在运行时选择要使用的格式。
如果您可以管理它,将应用限制在单个 APK 具有多项优势,包括:
- 发布和测试更加轻松
- 只需要维护一个代码库
- 您的应用可以适应设备配置的变化
- 可以跨设备执行应用恢复
- 您不必担心市场偏好、从一个 APK“升级”到下一个 APK 的行为,或者哪个 APK 与哪类设备相匹配
本节课的后续内容假设您已经研究了这个主题,认真学习了链接资源中的资料,并确定创建多个 APK 是适合您应用的正确方法。
制作您的需求图表
Android 开发者指南在 supports-gl-texture 页面上提供了一些常见受支持纹理的便捷参考。本页面还包含一些有关哪些手机(或手机系列)支持哪些特定纹理格式的提示。请注意,通常来说,让您的某个 APK 支持 ETC1 是一个很好的做法,因为所有支持 OpenGL ES 2.0 规范的 Android 设备都支持该纹理格式。
由于大多数 Android 设备都支持不止一种纹理格式,因此您需要确立一个优先顺序。制作一个包含应用将支持的所有格式的图表。最左边的单元格将具有最低的优先级(这可能是 ETC1,对于性能和兼容性而言,这是不错的默认值)。然后在图表中着色,使每个单元格代表一个 APK。
ETC1 | ATI | PowerVR |
在图表中着色不仅可以让本指南看起来不那么单调,还可以让团队内部的沟通更加容易,因为现在您可以简单地将每个 APK 称为“蓝色”“绿色”或“红色”,而不是“支持 ETC1 纹理格式的那个”等。
将所有通用代码和资源放在一个库项目中
无论您是要修改现有的 Android 应用还是从头开始创建 Android 应用,这都是您应该执行的第一项也是最重要的一项代码库操作。放入库项目中的所有内容都只需要更新一次(例如经过本地化的字符串、颜色主题背景、共享代码中修复的问题),这样可以缩短开发时间并减少发生原本很容易避免的错误的可能性。
注意:尽管有关如何创建和包含库项目的实现详情不在本课的讨论范围内,但阅读创建 Android 库有助于您跟上学习进度。
如果要将现有应用转换为支持多 APK,请搜索代码库,找出在不同 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 的应用时,系统会使用一些简单的规则来选择正确的 APK:
- 清单必须显示特定的 APK 符合条件
- 在符合条件的 APK 中,选择版本最高的一个
- 如果市场上的设备支持您的 APK 中列出的任一纹理格式,该设备就会被视为符合条件
关于 GL 纹理,最后一条规则很重要。例如,这意味着您应该非常谨慎地在同一应用中使用不同的 GL 格式。比如,如果您要在 99% 的情况下使用 PowerVR,而要在启动画面上使用 ETC1,则您的清单必须表明支持这两种格式。仅支持 ETC1 的设备会被视为兼容,并且仍会下载您的应用,但用户会看到一些令人紧张的崩溃消息。常见的情况是,如果您要使用多个 APK,并根据 GL 纹理支持将它们定位到不同的设备,则每个 APK 将采用一种纹理格式。
这实际会使纹理支持与多 APK 的其他两个维度(API 级别和屏幕尺寸)略有不同。任何给定的设备都只有一个 API 级别和一个屏幕尺寸,而具体的支持范围取决于 APK。对于纹理,APK 通常仅支持一种纹理,而设备会支持多种纹理。当一个设备支持多个 APK 时,经常会出现重叠的情况,但解决方法都一样:根据版本代码来选择。
以几个设备为例,看看每个设备可以支持几个之前定义的 APK。
FooPhone | Nexus S | Evo |
ETC1 | ETC1 | ETC1 |
PowerVR | ATI TC |
假设 PowerVR 和 ATI 格式在可用时均优于 ETC1,而不是根据“最高版本号胜出”规则得出的,如果我们在每个 APK 中设置 versionCode 属性,使得红色 ≥ 绿色 ≥ 蓝色,那么在支持它们的设备上,系统将始终选择红色和绿色,而不是蓝色,并且如果所选择的设备同时支持红色和绿色和红色。
为了便于区分各个 APK,制定有效的版本代码方案非常重要。您可以在我们的开发者指南的“版本代码”部分找到推荐的方案。由于这组示例 APK 只涉及 3 个可能维度中的一个,因此将每个 APK 隔开 1000 然后再从其递增就已足够。相应版本代码可能类似如下:
蓝色:1001, 1002, 1003, 1004...
绿色:2001, 2002, 2003, 2004...
红色:3001, 3002, 3003, 3004...
综上所述,您的 Android 清单可能如下所示:
蓝色:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1001" android:versionName="1.0" package="com.example.foo"> <supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" /> ...
绿色:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="2001" android:versionName="1.0" package="com.example.foo"> <supports-gl-texture android:name="GL_AMD_compressed_ATC_texture" /> ...
红色:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="3001" android:versionName="1.0" package="com.example.foo"> <supports-gl-texture android:name="GL_IMG_texture_compression_pvrtc" /> ...
检查发布前核对清单
在上传到 Google Play 之前,请仔细检查以下各项内容。请注意,这些内容都是与多个 APK 相关的,并不能代表上传到 Google Play 的所有应用的完整核对清单。
- 所有 APK 必须具有相同的软件包名称
- 所有 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: '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 定位到正确的设备。恭喜您大功告成了!