The Android Developer Challenge is back! Submit your idea before December 2.

支持 64 位架构

2019 年 8 月 1 日起,您在 Google Play 上发布的应用将需要支持 64 位架构。64 位 CPU 能够为您的用户提供更快、更丰富的体验。添加 64 位的应用版本可以提高性能、为未来创新创造条件,并为仅支持 64 位的设备做好准备。

本指南介绍了您可以立即采取的步骤,以确保您的 32 位应用为支持 64 位设备做好准备。

评估应用

如果您的应用完全使用 Java 编程语言或 Kotlin 编写(包括所有库和 SDK),那么应用已经可以用于 64 位设备了。如果您的应用使用了任何原生代码,或者您不确定应用是否使用了这类代码,那么您需要评估应用并采取措施。

快速状态检查

要检查应用是否已满足 64 位要求,一种快速的方法是前往 Play 管理中心查看现有的版本,确定它们是否合规:

如果您的草稿版本存在与 64 位要求相关的问题,Play 管理中心也会显示相应的警告。请看以下示例:

如果您看到提醒,请按照提醒下面的步骤操作,让应用做好支持 64 位的准备。

应用是否使用了原生代码?

首先需要检查您的应用是否使用了任何原生代码。如果符合以下条件,那么应用就使用了原生代码:

  • 在应用中使用了任何 C/C++(原生)代码。
  • 与任何第三方原生库关联。
  • 由使用原生库的第三方应用编译器编译而成。

应用是否包含 64 位库?

要检查是否包含 64 位库,最简单的方法就是检查 APK 文件的结构。编译时,APK 会和应用所需的所有原生库打包在一起。原生库根据 ABI 存储在不同的文件夹中。无需支持所有 64 位架构,但对于所支持的每种原生 32 位架构,您必须包含相应的 64 位架构。

对于 ARM 架构,32 位库位于 armeabi-v7a 中。对应的 64 位库位于 arm64-v8a 中。

对于 x86 架构,请查找 x86 文件夹(32 位)和 x86_64 文件夹(64 位)。

首先要确保这两个文件夹中都有原生库。总结如下:

平台 32 位库文件夹 64 位库文件夹
ARM lib/armeabi-v7a lib/arm64-v8a
x86 lib/x86 lib/x86_64

请注意,根据您的应用,每个文件夹中的库集可能完全相同,也可能不完全相同。目标是确保您的应用能够在仅限 64 位的环境中正常运行。

通常情况下,同时针对 32 位和 64 位架构编译的 APK 或软件包具有这两种 ABI 的文件夹,每个文件夹中都有相应的原生库集。如果不支持 64 位,那么您很可能会看到 32 位 ABI 文件夹,但不会看到 64 位文件夹。

使用 APK 分析器查找原生库

APK 分析器这款工具可以用来对编译的 APK 进行各个方面的评估。针对我们目前讨论的情况,我们将使用该工具来查找原生库,并确保存在 64 位库。

  1. 打开 Android Studio,然后打开任一项目
  2. 在菜单中,依次选择 Build > Analyze APK…

    启动 APK 分析器

  3. 选择您要评估的 APK。

  4. 查看 lib 文件夹,您可以在其中找到所有“.so”文件。如果在您的应用中找不到任何“.so”文件,则说明应用已准备就绪,而无需采取进一步措施。如果您看到 armeabi-v7ax86,则说明您有 32 位库。

  5. 检查 arm64-v8ax86_64 文件夹中是否有类似的“.so”文件。

    启动 APK 分析器

  6. 如果您没有任何 arm64-v8ax86_64 库,则需要更新编译流程以开始在 APK 中编译并打包这些工件。

  7. 如果您已经看到这两个库均已打包,则可以跳至在 64 位设备上测试应用

通过解压缩 APK 查找原生库

APK 文件的结构类似于 zip 文件,也可以像 zip 文件一样解压缩。如果您更喜欢使用命令行或任何其他解压缩工具,则解压缩 APK 对您来说也合适。

只需解压缩 APK 文件(根据您的解压缩工具,您可能需要将其重命名为 .zip)并按照上述指南浏览所解压缩的文件,即可确定您是否为 64 位设备做好了准备。

例如,您可以从命令行中运行以下命令:

:: Command Line
    > zipinfo -1 YOUR_APK_FILE.apk | grep \.so$
    lib/armeabi-v7a/libmain.so
    lib/armeabi-v7a/libmono.so
    lib/armeabi-v7a/libunity.so
    lib/arm64-v8a/libmain.so
    lib/arm64-v8a/libmono.so
    lib/arm64-v8a/libunity.so
    

请注意,此示例中存在 armeabi-v7a 库和 arm64-v8a 库,这意味着应用支持 64 位架构。

使用 64 位库编译应用

以下是有关编译 64 位库的说明。不过请注意,以下内容仅介绍了如何编译您能够在源代码的基础上进行编译的代码和库。

如果您使用任何外部 SDK 或库,请确保按照上述步骤使用 64 位版本。如果 64 位版本不可用,请与相应 SDK 或库的所有者联系,并在规划如何支持 64 位设备时将这一点考虑在内。

使用 Android Studio 或 Gradle 进行编译

大多数 Android Studio 项目都使用 Gradle 作为底层编译系统,因此本部分适用于这两种情况。针对原生代码启用编译很简单,只需将 arm64-v8a 和/或 x86_64(视您要支持的架构而定)添加到应用的“build.gradle”文件中的 ndk.abiFilters 设置中即可:

// Your app's build.gradle
    apply plugin: 'com.android.app'

    android {
       compileSdkVersion 27
       defaultConfig {
           appId "com.google.example.64bit"
           minSdkVersion 15
           targetSdkVersion 28
           versionCode 1
           versionName "1.0"
           ndk.abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
    // ...
    

使用 CMake 进行编译

如果您的应用是使用 CMake 编译的,则可以通过将 arm64-v8a 传递到“-DANDROID_ABI”参数来针对 64 位 ABI 进行编译:

:: Command Line
    > cmake -DANDROID_ABI=arm64-v8a … or
    > cmake -DANDROID_ABI=x86_64 …
    

在使用 externalNativeBuild 时,此方法无效。请参阅使用 Gradle 进行编译部分。

使用 ndk-build 进行编译

如果您的应用是使用 ndk-build 编译的,则可以使用 APP_ABI 变量修改“Application.mk”文件,从而针对 64 位 ABI 进行编译:

APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
    

在使用 externalNativeBuild 时,此方法无效。请参阅使用 Gradle 进行编译部分。

将 32 位代码移植到 64 位架构

如果您的代码已经可以在桌面或 iOS 平台上运行,则不需要针对 Android 做额外的工作。如果这是第一次针对 64 位系统编译您的代码,那么您需要解决的主要问题是指针不再适合 int 这样的 32 位整数类型。您将需要更新以 intunsigneduint32_t 等类型存储指针的代码。在 Unix 系统上,long 匹配的是指针大小,但在 Windows 上并非如此,因此您应该改用释意类型 uintptr_tintptr_t。使用 ptrdiff_t 类型来存储两个指针之间的差异。

您应该始终选择使用 <stdint.h> 中定义的特定固定宽度整数类型,而不是 intlong 等传统类型,即便对于非指针也应如此。

使用以下编译器标记来捕捉代码在指针和整数之间转换不正确的情况:

-Werror=pointer-to-int-cast
    -Werror=int-to-pointer-cast
    -Werror=shorten-64-to-32
    

具有 int 字段(包含指向 C/C++ 对象的指针)的 Java 类也有同样的问题。在 JNI 源代码中搜索 jint,并确保切换到 long(Java 端)和 jlong(C++ 端)。

对于 64 位代码而言,隐式函数声明的危险性要高得多。C/C++ 假定隐式声明的函数(即编译器未检测到声明的函数)的返回类型为 int。如果函数的实际返回类型是指针,那么在 32 位系统上是可行的,因为在 32 位系统中指针的类型为 int,但在 64 位系统中,编译器会丢弃指针的上半部分。例如:

// This function returns a pointer:
    // extern char* foo();

    // If you don't include a header that declares it,
    // when the compiler sees this:
    char* result = foo();

    // Instead of compiling that to:
    result = foo();

    // It compiles to something equivalent to:
    result = foo() & 0xffffffff;

    // Which will then cause a SIGSEGV if you try to dereference `result`.
    

以下编译器标记会将隐式函数声明警告变成错误,以便您能够更轻松地查找和解决此问题:

-Werror=implicit-function-declaration
    

如果您有内联汇编程序,您需要重新编写该程序或使用普通的 C/C++ 实现。

如果您对类型大小进行了硬编码(例如,8 或 16 字节),请使用等效的 sizeof(T) 表达式(例如 sizeof(void*))来替换它们。

如果需要有条件地编译不同于 64 位的 32 位代码,则对于一般性的 32/64 差异,您可以使用 #if defined(__LP64__),对于 Android 支持的具体架构,可以使用 __arm____aarch64__ (arm64)、__i386__ (x86) 和 __x86_64__

您需要调整 printfscanf 这类函数的格式字符串,因为传统的格式说明符不允许以对 32 位和 64 位设备都正确的方式来指定 64 位类型。<inttypes.h> 中的 PRISCN 宏解决了此问题,PRIxPTRSCNxPTR 用于写入/读取十六进制指针,PRId64SCNd64 用于以可移植的方式写入/读取 64 位值。

在移位时,您可能需要使用 1ULL 而非 1(只有 32 位)来获取要移位的 64 位常数。

利用 Android App Bundle 减少大小的增加

在应用中添加 64 位架构支持可能会导致 APK 大小增加。强烈建议您利用 Android APP Bundle 功能,以尽可能减小在同一 APK 中同时包含 32 位和 64 位原生代码时对 APK 大小的影响。

将应用改为使用 Android App Bundle 可以实际缩减 APK 的大小,使其小于现在的大小。

游戏开发者

我们知道,迁移第三方游戏引擎是一个准备时间很长且耗费人力的过程。值得庆幸的是,3 个最常用的引擎目前都支持 64 位:

  • Unreal(自 2015 年起)
  • Cocos2d(自 2015 年起)
  • Unity(自 2018 年起)

Unity 开发者

升级到支持的版本

Unity 从版本 2018.2 2017.4.16 开始提供 64 位支持。

如果您发现自己使用的 Unity 版本不支持 64 位,请确定要升级到的版本,并按照 Unity 提供的指南迁移您的环境,同时确保将您的应用升级到可以编译 64 位库的版本。Unity 建议您通过升级到该编辑器的最新 LTS 版本获取最新的功能和更新。

以下图表概述了各种 Unity 版本以及您应该执行的操作:

Unity 版本 版本是否支持 64 位? 建议执行的操作

2018.4 (LTS)

✔️

(待发布)确保您的编译设置能够输出 64 位库。

2018.3

✔️

确保您的编译设置能够输出 64 位库。

2018.2

✔️

确保您的编译设置能够输出 64 位库。

2018.1

提供实验性的 64 位支持。

2017.4 (LTS)

✔️

2017.4.16 开始支持。确保您的编译设置能够输出 64 位库。

2017.3

✖️

升级到支持 64 位的版本。

2017.2

✖️

升级到支持 64 位的版本。

2017.1

✖️

升级到支持 64 位的版本。

<=5.6

✖️

升级到支持 64 位的版本。

将编译设置更改输出 64 位库

如果您使用的 Unity 版本支持 64 位的 Android 库,则可以通过调整编译设置来生成 64 位版本的应用。您还需要使用 IL2CPP 后端作为 Scripting Backend(详见此处)。要将您的 Unity 项目设置为编译 64 位架构,请执行以下操作:

  1. 转到 Build Settings,然后验证 Unity 符号是否在 Android 平台旁边,以确保您是在针对 Android 进行编译。**
    1. 如果 Unity 符号不在 Android 平台旁边,请选择 Android,然后点击 Switch Platform
  2. 点击 Player Settings

    Unity 中的 Player Settings

  3. 依次转到 Player Settings Panel > Settings for Android > Other Settings > Configuration

  4. Scripting Backend 设为 IL2CPP

  5. 依次选择**“Target Architectures”> ARM64 复选框。

    在 Unity 中设置 Target Architectures

  6. 照常进行编译!

请注意,针对 ARM64 进行编译需要您专门针对该平台编译您的所有资源。请按照 Unity 提供的缩减 APK 大小指南操作,并考虑利用 Android APP Bundle 功能来减少大小的增加。

多 APK 和 64 位合规性

如果您要使用 Google Play 的多 APK 支持来发布应用,请注意需要在发布层面评估是否符合 64 位要求。不过,如果 APK 或 app bundle 不会分发给搭载 Android 9 Pie 或更高版本的设备,则 64 位要求不适用。

如果您的某个 APK 被标记为不合规,并且该 APK 版本较低,已无法使其合规,则一种策略是在该 APK 清单的 uses-sdk 元素中添加 maxSdkVersion="27" 属性。这样一来,此 APK 将不会被分发给搭载 Android 9 Pie 或更高版本的设备,因而也就不会再妨碍合规。

RenderScript 和 64 位合规性

如果您的应用使用 RenderScript 并且是通过较旧版本的 Android 工具编译的,该应用可能会存在 64 位合规性问题。使用低于 21.0.0 版的编译工具时,编译器可能会将位码生成到外部 .bc 文件中。64 位架构不再支持这些旧版 .bc 文件,因此,如果您的 APK 中有这类文件,就会造成合规性问题。

要解决此问题,请移除项目中的所有 .bc 文件,将环境升级到 build-tools-21.0.0 或更高版本,并将 Android Studio 中的 renderscriptTargetApi 设置为 21+,以指示编译器不要生成 .bc 文件。然后,重新编译您的应用,检查是否有 .bc 文件,然后再上传到 Play 管理中心。

在 64 位硬件上测试应用

64 位版本的应用应提供与 32 位版本相同的质量和功能集。请测试您的应用,以确保使用最新的 64 位设备的用户能够在您的应用中获得精彩的体验。

要开始测试您的应用,您要有支持 64 位的设备。支持 64 位的热门设备各式各样,例如 Google 的 Pixel 以及其他旗舰设备。

要测试您的 APK,最简单的方法是使用 adb 安装应用。在大多数情况下,您可以提供 --abi 作为参数,表示要将哪些库安装到设备上。这样即可在设备上安装仅包含 64 位库的应用。

:: Command Line
    # A successful install:
    > adb install --abi armeabi-v7a YOUR_APK_FILE.apk
    Success

    # If your APK does not have the 64-bit libraries:
    > adb install --abi arm64-v8a YOUR_APK_FILE.apk
    adb: failed to install YOUR_APK_FILE.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113]

    # If your device does not support 64-bit, an emulator, for example:
    > adb install --abi arm64-v8a YOUR_APK_FILE.apk
    ABI arm64-v8a not supported on this device
    

成功安装后,请照常测试应用,以确保其质量与 32 位版本相同。

发布

如果您觉得应用已准备就绪,请照常发布。与往常一样,请继续遵循有关部署应用的最佳做法。我们建议利用封闭式测试轨道向有限数量的用户发布应用,以确保应用的质量始终如一。

在发布重大更新时,请务必先在支持 64 位的设备上进行全面测试,然后再向更多的受众群体发布。