Android NDK开发详解设备兼容性之支持 64 位架构

    • 评估应用
      • 状态速查
      • 您的应用是否使用了原生代码?
      • 您的应用是否包含 64 位库?
      • 使用 APK 分析器查找原生库
      • 通过解压缩 APK 查找原生库
    • 使用 64 位库构建应用
      • 使用 Android Studio 或 Gradle 构建
      • 使用 CMake 构建
      • 使用 ndk-build 构建
      • 将 32 位代码移植到 64 位架构
      • 利用 Android App Bundle 减少大小增加量
      • 游戏开发者
      • Unity 开发者
    • 多 APK 和 64 位合规性
    • RenderScript 和 64 位合规性
    • 在 64 位硬件上测试应用
      • 仅支持 64 位架构的设备
      • 其他设备选项
      • 安装并测试您的应用
      • 检查已知兼容性问题
    • 发布

{0}
在 Google Play 上发布的应用需要支持 64 位架构。为应用添加 64 位版本,不仅可以提升性能,还能针对仅支持 64 位架构的设备做好准备。

以下步骤可确保您的 32 位应用支持 64 位设备。

评估应用

如果您的应用仅使用以 Java 或 Kotlin 编程语言编写的代码(包括所有库或 SDK),则该应用支持 64 位设备。如果您的应用使用了任何原生代码,或者您不确定是否使用了原生代码,请评估您的应用。

状态速查

前往 Play 管理中心查看应用的现有版本,确定其是否合规。

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

如果系统显示提醒,请通过以下步骤确保您的应用兼容 64 位设备。

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

如果您的应用符合以下情况,则表明其使用了原生代码:

使用了任何 C/C++(原生)代码。
与任何第三方原生库关联。
通过使用原生库的第三方应用构建程序构建而成。

您的应用是否包含 64 位库?

检查 APK 文件的结构。在构建时,APK 会与应用所需的所有原生库打包在一起。原生库会根据 ABI 存储在不同的文件夹中。您的应用不一定要支持所有 64 位架构,但对于支持的每种原生 32 位架构,应用都必须包含相应的 64 位架构。

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

对于 x86 架构,32 位库位于 x86 中,64 位库则位于 x86_64 中。

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

平台32 位库文件夹64 位库文件夹ARMlib/armeabi-v7alib/arm64-v8ax86lib/x86lib/x86_64

请注意,每个文件夹中的一套库可能完全相同,也可能不完全相同,具体取决于应用。您应达到的目标是确保应用能够在仅支持 64 位架构的环境中正常运行。

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

使用 APK 分析器查找原生库

APK 分析器是一款可用于对所构建的 APK 进行各方面评估的工具。请使用该工具查找原生库,并确定是否具备 64 位库。

打开 Android Studio,然后打开任一项目。
从菜单中依次选择 Build > Analyze APK…

启动 APK 分析器

选择您要评估的 APK。

查看 lib 文件夹,其中会托管 .so 文件(如有)。如果没有,则表示您的应用支持 64 位设备,您无需采取进一步措施。如果您看到 armeabi-v7a 或 x86,则说明您有 32 位库。

检查 arm64-v8a 或 x86_64 文件夹中是否有类似的 .so 文件。

启动 APK 分析器

如果您没有任何 arm64-v8a 或 x86_64 库,请更新构建流程以开始在 APK 中构建和打包这些工件。

如果您看到 32 位库和 64 位库均已打包到软件包中,则可以跳至在 64 位硬件上测试应用。

通过解压缩 APK 查找原生库

APK 文件的结构类似于 ZIP 文件。使用命令行或任何其他解压缩工具解压缩 APK 文件。您可能需要将文件重命名为 .zip,具体取决于您使用的解压缩工具。

浏览解压缩后的文件,按照上述指南确定您的应用是否支持 64 位设备。您可以从命令行中运行如下示例命令:

:: Command Line> zipinfo -1 YOUR_APK_FILE.apk | grep \.so$lib/armeabi-v7a/libmain.solib/armeabi-v7a/libmono.solib/armeabi-v7a/libunity.solib/arm64-v8a/libmain.solib/arm64-v8a/libmono.solib/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 设置:

Groovy

// Your app's build.gradleplugins {id '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'// ...

Kotlin

// Your app's build.gradleplugins {id("com.android.app")}android {compileSdkVersion(27)defaultConfig {appId = "com.google.example.64bit"minSdkVersion(15)targetSdkVersion(28)versionCode = 1versionName = "1.0"ndk {abiFilters += listOf("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 位整数类型。

更新以 int、unsigned 或 uint32_t 等类型存储指针的代码。在 Unix 系统上,long 对应的是指针大小,但在 Windows 上并非如此。请改用释意类型 uintptr_t 或 intptr_t。如需存储两个指针之间的差异,请使用 ptrdiff_t 类型。

您应该始终选择使用 中定义的特定固定宽度整数类型,而不是 int 或 long 等非固定宽度类型,即便对于非指针也应如此。

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

-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++ 端)。

注意:因指针被截断而引起的崩溃将表现为 SIGSEGV,其中错误地址的前 32 位全部为零。
对于 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 支持的具体架构,可以使用 armaarch64 (arm64)、i386 (x86) 和 x86_64

请调整类似 printf 或 scanf 的函数的格式字符串,因为如果使用传统的格式说明符,您无法以一种对 32 位和 64 位设备都正确的方式来指定 64 位类型。您可利用 中的 PRI 和 SCN 宏来解决此问题,PRIxPTR 和 SCNxPTR 分别用于写入/读取十六进制指针,PRId64 和 SCNd64 分别用于以可移植的方式写入/读取 64 位值。

在移位时,您可能需要使用 1ULL 来获取要移位的 64 位常数,而不能使用仅支持 32 位的 1。

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

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

游戏开发者

三大最常用的引擎目前都支持 64 位架构:

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

Unity 开发者

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

下面的图表概述了 Unity 的各个版本以及您应该采取的措施:

Unity 版本版本是否支持 64 位?建议采取的措施2020.x✔️确保您的构建设置能够输出 64 位库。2019.x✔️确保您的构建设置能够输出 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。若要为构建 64 位架构而设置 Unity 项目,请按以下步骤操作:

前往 Build Settings,然后确认 Unity 标志是否显示在 Platform 下的 Android 旁边,以确保您是在针对 Android 进行构建。 1. 如果 Unity 标志未显示在 Android 平台旁边,请选择 Android,然后点击 Switch Platform。
点击 Player Settings。

Unity 中的 Player Settings

前往 Player Settings Panel > Settings for Android > Other Settings > Configuration

将 Scripting Backend 设为 IL2CPP。

依次选择 Target Architecture > ARM64 复选框。

在 Unity 中设置目标架构

照常构建!

请注意,针对 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 位架构的严格环境中测试您的应用:

带有仅支持 64 位架构的系统映像的 Google Pixel
Android 模拟器
带有仅支持 64 位架构的系统映像的 Google Pixel
为了便于应用开发和测试,我们为某些 Pixel 设备提供了包含仅支持 64 位架构的严格环境的特殊系统映像。这些仅支持 64 位架构的映像最初是与适用于 Android 13 和 14 预览版的标准出厂系统映像同时提供的,但您可以在测试应用的 64 位架构兼容性时继续使用这些映像。

注意:虽然您可以在基于不同 Android 版本的仅支持 64 位架构的系统映像之间切换单个测试设备,但必须先将设备恢复到最新的公开稳定 build,然后再刷写仅支持 64 位架构的另一个 Android 平台版本映像。
获取仅支持 64 位架构的映像
与出厂系统映像类似,您可以使用 Android 刷写工具或通过手动刷写设备将仅支持 64 位架构的映像刷写到设备上。

注意:虽然 Pixel 7 和 Pixel 7 Pro 是首批在发布时仅支持 64 位应用的 Android 手机,但这些设备在发布时搭载了 Android 13 系统映像,其中包括 32 位文件系统和 32 位系统库,用于处理兼容性极端情况。因此,如果您有 Pixel 7 或 Pixel 7 Pro,以及带有仅支持 64 位架构的可用系统映像的 Pixel 设备,请使用带有仅支持 64 位架构的映像的设备创建可控性更强的环境,以便测试 64 位兼容性。
使用 Android 刷写工具刷写设备
Android 刷写工具可让您将系统映像安全地刷写到受支持的 Pixel 设备上。Android 刷写工具可以在任何支持 WebUSB 的网络浏览器(如 Chrome 或 Edge 79 及更高版本)上使用。

Android 刷写工具会引导您逐步完成设备刷写过程。您无需安装工具,但需要解锁设备,并在开发者选项中启用 USB 调试。如需查看完整说明,请参阅 Android 刷写工具文档。

通过 USB 连接设备,然后根据要刷写的系统映像类型,使用以下某个链接访问 Android 刷写工具,并按照屏幕上的说明操作:

Android 14(Beta 版 5.2)仅支持 64 位架构的系统映像

选择您要尝试刷写的设备:

Pixel 4a (5G)
Pixel 5
Pixel 6
Pixel 6 Pro
Android 13(QPR3 Beta 版 3.2)仅支持 64 位架构的系统映像

选择您要尝试刷写的设备:

Pixel 4a (5G)
Pixel 5
Pixel 6
Pixel 6 Pro
手动刷写设备
您还可以下载最新的系统映像,并手动将其刷入您的设备。请参见下表,下载适合您的测试设备的系统映像。如果您需要精确控制测试环境或者需要经常重新安装(例如,在执行自动测试时),手动刷写设备会非常有用。

在备份设备数据并下载以下匹配的系统映像后,您就可以将映像刷入设备。

您可以随时选择还原为最新的公开版 build。

适用于 Android 14(Beta 版 5.3)的仅支持 64 位架构的出厂映像
这些映像提供严格的仅限 64 位环境,用于测试 64 位应用兼容性。这些仅限 64 位的配置仅供开发者使用。

设备下载链接SHA-256 校验和Pixel 4a (5G)bramble_beta_64-upb5.230623.006-factory-7e6731fa.zip7e6731fab811ae389f5ff882d5c5a2b8b942b8363b22bbcc038b39d7c539e60aPixel 5redfin_beta_64-upb5.230623.006-factory-c4da6a19.zipc4da6a19086a02f2cd2fa7a4054e870916954b8e5a61e9a07ee942c537e4b45aPixel 6oriole_beta_64-upb5.230623.006-factory-98943384.zip98943384284cbc7323b8867d84c36151757f67ae7633012fb69cb5d6bec2b554Pixel 6 Proraven_beta_64-upb5.230623.006-factory-67ec40be.zip67ec40be5bd05a40fa5dabc1ce6795aae75d1904193d52e2da00425ed7cb895b

适用于 Android 13 (QPR3 Beta 版 3.2) 的仅支持 64 位架构的出厂映像
这些映像提供严格的仅限 64 位环境,用于测试 64 位应用兼容性。这些仅限 64 位的配置仅供开发者使用。

设备下载链接SHA-256 校验和Pixel 4a (5G)bramble_64-t3b3.230413.009-factory-b4be4092.zipb4be40924f62c3c2b3ed20a9f7fa4303aa9c39649d778eb96f86c867fe3ae59aPixel 5redfin_64-t3b3.230413.009-factory-6e5e027a.zip6e5e027a4f64f9f786db9bb69d50d1a551c3f6aad893ae450e1f8279ea1b761aPixel 6oriole_64-t3b3.230413.009-factory-becb9b81.zipbecb9b81a5bddad67a4ac32d30a50dcb372b9d083cb7c046e5180510e479a0b8Pixel 6 Proraven_64-t3b3.230413.009-factory-b0ef544e.zipb0ef544ed2312ac44dc827f24999281b147c11d76356c2d06b2c57a191c60480

还原为公开版 build
您可以使用 Android 刷写工具刷入出厂映像,也可以从适用于 Nexus 和 Pixel 设备的出厂映像页面获取出厂规格系统映像,然后手动将其刷入设备。

警告:从预览版 build(开发者预览版或 Beta 版 build)还原为公开版 build 时,需要完全重置设备,而这会移除设备上的所有用户数据设备。因此,请务必先备份数据。
Android 模拟器
从 Android 12(API 级别 31)开始,Android 模拟器系统映像仅支持 64 位架构。使用版本为 Android 12(API 级别 31)或更高版本的系统映像创建 Android 虚拟设备 (AVD),以获取仅支持 64 位架构的严格环境进行应用测试。

其他设备选项

如果您没有上述任何设备,或者无法使用 Android 模拟器,那么下一个最佳选项就是使用支持 64 位架构的设备,例如 Google Pixel 或其他设备制造商近期推出的其他旗舰设备。

安装并测试您的应用

最简单的 APK 测试方法就是使用 Android 调试桥 (adb) 安装该应用。大多数情况下,您可以提供 –abi 作为参数,用以指示要将哪些库安装到设备上。这样在设备上安装该应用时便会仅包含 64 位库。

:: Command Line# A successful install:> adb install --abi armeabi-v7a YOUR_APK_FILE.apkSuccess# If your APK does not have the 64-bit libraries:> adb install --abi arm64-v8a YOUR_APK_FILE.apkadb: 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.apkABI arm64-v8a not supported on this device

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

检查已知兼容性问题

在测试时,请检查您的应用是否存在以下问题:在 64 位设备上运行应用时,这些问题会影响您的应用。即使您的应用不直接依赖受影响的库,但应用依赖项中的第三方库和 SDK 也可能会。

SoLoader
如果您使用的是原生代码加载器 SDK SoLoader,请更新到 v0.10.4 或更高版本。如果您的应用使用的 SDK 依赖于 SoLoader,请务必同时将受影响的 SDK 更新到最新稳定版本。

SoLoader v0.9.0 及更低版本假设系统库位于 /vendor/lib:/system/lib 中。在存在该路径的 Pixel 7 等设备中,无法观测到此 bug,但这种假设会导致只在 /vendor/lib64:/system/lib64 中有系统库的设备发生崩溃。

如需详细了解如何解决此问题以及由 SoLoader 引起的其他问题,请参阅 Google 帮助中心内的相应回答。

OpenSSL
如果您使用的是 OpenSSL 库,请更新到 OpenSSL 1.1.1i 或更高版本。如果您的应用使用的 SDK 使用 HTTPS 提供通信,或使用了依赖于 OpenSSL 的其他 SDK,请务必同时将 SDK 更新为使用较新版 OpenSSL 的最新版本。如果无法获取这样的 SDK,请与 SDK 提供方联系。

ARMv8.3 PAC 功能通过在运行时验证指针来实现由硬件辅助的控制流完整性。较低版本的 OpenSSL 无法正常使用这些功能,会导致在搭载基于 ARMv8.3a 及更高版本的处理器的所有设备上发生运行时崩溃。

如需详细了解如何解决此问题以及由 OpenSSL 引起的其他问题,请参阅 Google 帮助中心内的相应回答。

BTI
ARMv8.5 及更高版本使用分支目标指令 (BTI) 来帮助防范 JOP 攻击。如果旧版混淆 SDK 分支为使用 BTI 构建的库的随机偏移,可能会导致应用崩溃。由于指令已编码为 HINT,因此在不支持 BTI 的设备上无法观察到此 bug。

发布

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

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

本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。

最后更新时间 (UTC):2023-11-21。