如果您将应用发布到 Google Play,则应构建并上传一个 Android 应用捆绑包。这样做时,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 库 来快速入门。
如果您正在将现有应用转换为使用多个 APK 支持,请仔细检查您的代码库,查找每个本地化字符串文件、值列表、主题颜色、菜单图标和布局,这些文件和布局不会在 APK 之间发生变化,并将它们全部放入库项目中。不会改变多少的代码也应该放在库项目中。您可能会发现自己扩展这些类以从一个 APK 添加一两个方法。
另一方面,如果您是从头开始创建应用,请尽可能尝试首先在库项目中编写代码,然后仅在必要时将其移到单个 APK 中。从长远来看,这比将其添加到一个、然后是另一个、然后是另一个,然后几个月后试图弄清楚这个 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 不必重新实现“通用”任务,例如初始化分析、运行许可检查以及任何其他不会从一个 APK 更改到另一个 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 属性设置为 red ≥ green ≥ blue,那么 Red 和 Green 将始终在支持它们的设备上优先于 Blue,如果出现支持 Red 和 Green 的设备,则选择 red。
为了将所有 APK 保持在单独的“轨道”上,拥有一个好的版本代码方案非常重要。推荐的方案可以在我们开发者指南的版本代码区域找到。由于 APK 示例集只处理三个可能维度的其中一个,因此用 1000 分隔每个 APK,然后递增就足够了。这可能看起来像
蓝色: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 上的 XLARGE 屏幕的 APK 将不会被任何人看到)
- 每个 APK 的清单必须至少在一个受支持的屏幕、OpenGL 纹理或平台版本中是唯一的。
- 尝试在至少一台设备上测试每个 APK。如果没有,您的开发机器上就有最可定制的设备模拟器之一。尽情使用吧!
在将 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 的功能要求。由于大多数(如果不是全部)xlarge 设备是平板电脑,没有内置的电话硬件,因此 Google Play 会在这些情况下过滤掉此 APK,直到未来出现既足够大以报告为 xlarge 屏幕尺寸又拥有电话硬件的设备。
幸运的是,这可以通过在您的清单中添加以下内容来轻松解决
<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 针对的是预期的设备。恭喜,您完成了!