为不同的 GL 纹理创建多个 APK

如果您将应用发布到 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。

另一方面,如果您是从头开始创建应用程序,请尽量先在库项目中编写代码,然后只有在必要时才将其移到单个 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 不必重新实现“通用”任务,例如初始化分析、运行许可证检查以及任何其他不会从 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 的设备上,Red 和 Green 将始终优先于 Blue 被选择,并且如果将来出现同时支持 Red 和 Green 的设备,则将选择 red。

为了将所有 APK 保持在不同的“轨道”上,拥有良好的版本代码方案非常重要。推荐的方案可以在我们开发者指南的“版本代码”部分找到。由于 APK 示例集只处理三个可能维度中的一个,因此将每个 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 在 XLARGE 屏幕上的 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 功能需求。由于大多数(如果不是全部)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 正在针对目标设备。恭喜,您完成了!