认识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

4. 不同 NDK 版本目标系统库、头文件和编译器的差异

image.png

你可能感兴趣的:(认识NDK编译)