一、ABI与多架构支持:Android支持的ABI列表

做NDK开发,第一个绕不开的概念就是ABI。

ABI全称是Application Binary Interface,说白了就是你的C/C++代码编译成二进制后,跟Android系统之间的一套约定。这套约定包括指令集、寄存器使用、函数调用方式、数据对齐方式等等。不同CPU架构,ABI就不同。

我记得刚入行那会儿,第一次编译so库,直接扔到真机上跑,结果闪退。查了半天,原来是只编译了arm64-v8a,而测试机是32位的。嗯,从那以后,我再也不敢忽略ABI配置了。

1.1 Android支持的ABI列表

Android目前主流的ABI有四个,我列个表给你看:

ABI名称 对应架构 位数 常见设备
armeabi-v7a ARMv7 32位 老旧手机、低端平板
arm64-v8a ARMv8-A 64位 2015年后的主流手机
x86 Intel/AMD x86 32位 旧款Android模拟器、部分平板
x86_64 Intel/AMD x86_64 64位 新版模拟器、Chromebook

你可能会问:为什么没有armeabi了?

其实早期Android确实有armeabi(ARMv5),但Google从NDK r17开始就正式移除了它的支持。我个人建议,如果你的minSdkVersion >= 21,直接放弃armeabi-v7a都行,只保留arm64-v8a就够了。不过现实项目中,很多App还在兼容低端机,所以armeabi-v7a暂时还不能丢。

1.2 ANDROID_ABI变量

在CMake中,控制目标ABI的核心变量就是ANDROID_ABI。这个变量由Android NDK的CMake工具链自动设置,你不需要手动定义,但可以在CMakeLists.txt中读取它来做条件判断。

举个例子,我在一个项目中需要针对不同架构链接不同的预编译库:

# CMakeLists.txt
if(${ANDROID_ABI} STREQUAL "arm64-v8a")
    set(MY_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/libs/arm64-v8a)
elseif(${ANDROID_ABI} STREQUAL "armeabi-v7a")
    set(MY_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/libs/armeabi-v7a)
elseif(${ANDROID_ABI} STREQUAL "x86_64")
    set(MY_LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/libs/x86_64)
else()
    message(FATAL_ERROR "Unsupported ABI: ${ANDROID_ABI}")
endif()

add_library(my_native SHARED IMPORTED)
set_target_properties(my_native PROPERTIES
    IMPORTED_LOCATION ${MY_LIB_PATH}/libmy_native.so
)

这里有个坑,我曾经踩过:ANDROID_ABI的值是小写的,比如arm64-v8a,不是ARM64-V8A。大小写写错了,条件判断永远不成立,编译出来的so是空的。排查了半天,最后发现是字符串比较的问题。

⚠️ 注意: ANDROID_ABI 变量只在 NDK 的 CMake 工具链中有效。如果你直接用系统自带的 cmake 命令,没有加载 NDK 工具链,这个变量是未定义的。

1.3 多ABI构建配置

实际开发中,我们不可能只编译一个ABI。Google Play商店要求你最好覆盖所有主流架构,否则用户下载后可能无法运行。

在Android Gradle插件中,配置多ABI非常简单。你只需要在build.gradledefaultConfigproductFlavors中指定ndk.abiFilters

android {
    defaultConfig {
        ndk {
            // 指定要编译的ABI
            abiFilters "arm64-v8a", "armeabi-v7a", "x86_64"
        }
    }
}

如果你用的是CMake命令行方式,可以这样指定:

# 为arm64-v8a构建
cmake -DANDROID_ABI=arm64-v8a \
      -DANDROID_PLATFORM=android-21 \
      -DCMAKE_TOOLCHAIN_FILE=${NDK_PATH}/build/cmake/android.toolchain.cmake \
      ..

# 为armeabi-v7a构建
cmake -DANDROID_ABI=armeabi-v7a \
      -DANDROID_PLATFORM=android-16 \
      -DCMAKE_TOOLCHAIN_FILE=${NDK_PATH}/build/cmake/android.toolchain.cmake \
      ..

我个人习惯写一个shell脚本,一次性编译所有目标ABI:

#!/bin/bash
ABIS=("arm64-v8a" "armeabi-v7a" "x86_64")
for ABI in ${ABIS[@]}; do
    mkdir -p build/${ABI}
    cd build/${ABI}
    cmake -DANDROID_ABI=${ABI} \
          -DANDROID_PLATFORM=android-21 \
          -DCMAKE_TOOLCHAIN_FILE=${NDK_PATH}/build/cmake/android.toolchain.cmake \
          ../..
    make -j$(nproc)
    cd ../..
done

这样跑完,build/目录下就会生成三个子目录,每个里面都有对应的.so文件。你只需要把它们按目录结构放到app/src/main/jniLibs/下即可。

💡 小技巧: 如果你用Android Studio,可以在Build Variants面板中切换不同的ABI进行调试。但注意,模拟器建议用x86_64,真机用arm64-v8a,这样编译速度最快。

1.4 知识体系总览

下面这张图,我把本章的核心知识点串起来了。你看一眼就能明白ABI、ANDROID_ABI变量、多ABI构建之间的关系:

ABI与多架构支持知识体系 ABI(应用二进制接口) 支持的ABI列表 armeabi-v7a(32位ARM) arm64-v8a(64位ARM) x86(32位Intel) x86_64(64位Intel) ANDROID_ABI变量 由NDK工具链自动设置 在CMakeLists.txt中读取 用于条件判断/链接库 值:arm64-v8a等(小写) 多ABI构建配置 Gradle:abiFilters配置 CMake命令行:-DANDROID_ABI Shell脚本批量编译 输出到jniLibs目录 核心原则:按需编译,覆盖主流,兼顾兼容 armeabi-v7a + arm64-v8a 覆盖99%的Android设备

1.5 避坑指南

最后,我分享几个实战中容易踩的坑:

  • 不要一股脑编译所有ABI:我曾经为了省事,把四个ABI全编译了。结果APK体积直接翻倍。后来只保留arm64-v8a和armeabi-v7a,x86系列只在模拟器调试时用。
  • 注意32位与64位混合使用:如果你的App同时加载32位和64位的so,系统会直接崩溃。要么全32,要么全64,不能混搭。
  • abiFilters只过滤编译,不过滤打包:如果你在abiFilters中只写了arm64-v8a,但jniLibs目录下还有其他ABI的so,Gradle打包时仍然会全部打进APK。记得配合splits.abi或手动清理目录。

📌 一句话总结:ABI是NDK开发的基石。搞懂了ABI列表、ANDROID_ABI变量、多ABI构建配置,你的so库才能在不同设备上正确运行。别像我当年那样,编译完就跑,结果闪退到怀疑人生。


公众号:蓝海资料掘金营,微信deep3321