一、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是空的。排查了半天,最后发现是字符串比较的问题。
1.3 多ABI构建配置
实际开发中,我们不可能只编译一个ABI。Google Play商店要求你最好覆盖所有主流架构,否则用户下载后可能无法运行。
在Android Gradle插件中,配置多ABI非常简单。你只需要在build.gradle的defaultConfig或productFlavors中指定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/下即可。
1.4 知识体系总览
下面这张图,我把本章的核心知识点串起来了。你看一眼就能明白ABI、ANDROID_ABI变量、多ABI构建之间的关系:
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