认识NDK编译
一. Android ABI
不同的 Android 设备使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 与指令集的每种组合都有专属的应用二进制接口 Application Binary Interface ( ABI ),因此使用 NDK 生成在 Android 运行 .a 或 .so (都是二进制文件)就需要指定 ABI 。 目前 NDK 支持的 ABI 如下:
ABI | 支持的指令集 | CPU 架构 | 应用 |
---|---|---|---|
armeabi-v7a | armeabi 等 | ARM 32 位 | 手机 |
arm64-v8a | AArch64 等 | ARM 64 位 | 手机 |
x86 | x86 (IA-32) 等 | x86 32 位 | PC |
x86_64 | x86-64 等 | x86 64 位 | PC |
NDK 17 前支持 ARMv5 (armeabi) 以及 32 位和 64 位 MIPS,但 NDK r17 和 17 后不再支持。
1. 指定 ABI
Gradle
默认情况下,Gradle(无论是通过 Android Studio 使用,还是从命令行使用)会针对所有非弃用 ABI 进行构建。要限制应用支持的 ABI 集,可以使用 abiFilters。例如,要仅针对 64 位 ABI 进行构建,可以在 build.gradle 中设置以下配置:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
ndk-build
默认情况下,ndk-build 会针对所有非弃用 ABI 进行构建。可以通过在 Application.mk 文件中设置 APP_ABI 来定位特定 ABI。如下:
APP_ABI := arm64-v8a # Target only arm64-v8a
APP_ABI := all # Target all ABIs, including those that are deprecated.
APP_ABI := armeabi-v7a x86_64 # Target only armeabi-v7a and x86_64.
CMAKE
使用 CMake 时,一次只能针对一个 ABI 进行构建,并且必须明确指定 ABI。为此,如果需要使用 ANDROID_ABI ,则必须在命令行中指定(无法在 CMakeLists.txt 中设置)。例如:
$ cmake -DANDROID_ABI=arm64-v8a ...
$ cmake -DANDROID_ABI=armeabi-v7a ...
$ cmake -DANDROID_ABI=x86 ...
$ cmake -DANDROID_ABI=x86_64 ...
2. 在C/C++处理 CPU 功能区别 ABI
通常,在构建时使用#ifdef 及以下各项确定 ABI 最为方便:
- 对于 32 位 ARM ,使用 _ _ arm _ _
- 对于 64 位 ARM,使用 _ _ aarch64 _ _
- 对于 32 位 X86,使用 _ _ i386 _ _
- 对于 64 位 X86,使用 _ _ x86_64 _ _
二. 使用 NDK 构建项目
使用 NDK 编译代码主要有三种方式:
- 基于 Make 的 ndk-build。这种方式一般是配合 Android.mk 和 Application.mk 文件 是之前 Android Studio 使用的方式
- CMake 。这种方式基于 CMakeLists.txt 文件,是现在 Android Studio 默认使用的方式
- 独立工具链 toolchain。这种方式常用于交叉编译,常配合含有 configure 文件的项目的使用,例如 ffmpeg 编译 so
1. NDK 主要目录
NDK 编译主要与下面几个目录有关:
platforms
包含不同的 Android 版本目录,每个 Android 版本目录又包含 不同的 ABI 目录,在 ABI 目录里面有 /usr/lib 目录,包含常用的 .a 或 .so 库,在 /usr/include 里包含需要的头文件( ndk 16 后不保留头文件)。
sysroot
包含需要的 .a 或 .so 库和头文件,在 usr/lib 和 usr/include 和目录下(ndk 16 后两者都有,16和16前只有头文件)。
toolchain
包含编译(交叉编译)工具链的目录
build/tools
包含独立编译工具链脚本 make_standalone_toolchain.py 或者 make_standalone_toolchain.sh ,其作用是根据 Android API 和 ABI 集成目标系统库,目标系统头文件和编译器在指定的一个目录。当然使用这个脚本也是可以的,不过需要指定各个库、头文件的目录,通常这些都不再一个目录,比较麻烦。使用 make_standalone_toolchain 的方式是:
--arch 指定 cpu 架构 --api 指定系统版本 --install-dir 生成的编译工具链目录
例如
./make_standalone_toolchain.py \
--arch arm --api 21 --install-dir /tmp/android-21-toolchain
在 NDK 19 开始就不需要使用独立工具链了, 在 toolchains/llvm/ 下已经提供好了编译工具, 读者自行去下载进入目录看看,和独立工具链编译出来的结构非常类似,不过它比较全的是针对不同平台版本。
2. 用独立工具链编译代码
用独立工具链编译代码的时需要指定下面几个地方
- 目标处理器架构,可以用 --arch 标记来完成
- 目标系统头文件(与 Android API 有关)
- 目标系统库 (与 Android API 有关)
- 编译器 gcc 还是 clang
不同的 NDK 版本差异比较大,因此有时候同一个 configure,替换不同的 NDK 版本就不能编译,这其实可能是 NDK 差异导致的。一般的差异主要是 - 目标系统库和头文件的目录位置变化
- 是否使用独立编译工具链脚本 make_standalone_toolchain.py 或者是 make_standalone_toolchain.sh 生成的目录来指定库和头文件
- ndk 编译器 gcc or clang 的指定
3. 指定 NDK 提供的头文件和库
- --sysroot=XX
指定头文件和库,会到 XX/usr/include 目录下查找头文件和 XX/usr/lib 目录下查找 .a 或者 .so 库 - -isysroot YY
指定头文件,覆盖--sysroot的设置,会到 YY/usr/include 目录下查找头文件 - -isystem ZZ
指定头文件,作补充,不覆盖--sysroot的设置,会到 ZZ (全路径) 下查找头文件 - -I XX
指定头文件,优先级高,不覆盖--sysroot的设置,回到 XX (全路径) 下查找头文件 - -LXX
指定库,会到 XX 目录下查找库 - -lxx.xo
指定库,直接指定某个 so 库。
查找优先级 -I > isystem > isysroot