为不同 GL 纹理创建多个 APK

如果您将应用发布到 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 库来快速掌握。

如果您正在将现有应用程序转换为使用多个 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-red

创建项目后,将库项目作为引用添加到每个 APK 项目中。如果可能,请在库项目中定义您的启动 Activity,并在您的 APK 项目中扩展该 Activity。在库项目中定义一个启动 Activity,让您有机会将所有应用程序初始化放在一个地方,这样每个单独的 APK 就不必重新实现“通用”任务,例如初始化 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 属性设置为 red ≥ green ≥ blue,那么在支持它们的设备上,红色和绿色将始终优于蓝色被选择,如果出现同时支持红色和绿色的设备,则选择红色。

为了使您的所有 APK 保持在不同的“轨道”上,拥有一个良好的版本代码方案非常重要。推荐的方案可在我们开发者指南的版本代码区域找到。由于示例 APK 集只处理 3 个可能维度中的一个,因此将每个 APK 相隔 1000 并从那里递增就足够了。这可能看起来像:

蓝色:1001、1002、1003、1004...
绿色:2001、2002、2003、2004...
红色:3001、3002、3003、3004...

综上所述,您的 Android Manifests 可能会像下面这样:

蓝色

<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 都必须使用相同的证书签名
  • 仔细检查您的清单过滤器是否存在冲突信息(只在 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 正在定位目标设备。恭喜,您已完成!