Android NDK 支持使用 CMake 为您的应用程序编译 C 和 C++ 代码。本页介绍如何通过 Android Gradle 插件的 ExternalNativeBuild
或直接调用 CMake,使用 CMake 与 NDK 协同工作。
CMake 工具链文件
NDK 通过 工具链文件 支持 CMake。工具链文件是 CMake 文件,用于自定义工具链的行为以进行交叉编译。用于 NDK 的工具链文件位于 NDK 中的 <NDK>/build/cmake/android.toolchain.cmake
。
在调用 cmake
时,构建参数(如 ABI、minSdkVersion
等)在命令行上给出。有关支持参数的列表,请参阅 工具链参数 部分。
“新”工具链文件
早期的 NDK 尝试了一个新的工具链文件实现,该实现将减少使用 NDK 的工具链文件和使用内置 CMake 支持之间的行为差异。最终,这需要大量的努力(尚未完成),但实际上并没有改善行为,因此我们不再继续这样做。
“新”工具链文件与“旧版”工具链文件相比存在行为倒退。默认行为是推荐的工作流程。如果您使用的是 -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF
,我们建议从您的构建中删除该标志。新工具链文件从未与旧版工具链文件达到同等水平,因此可能存在行为倒退。
虽然我们建议不要使用新工具链文件,但目前没有计划将其从 NDK 中删除。这样做会破坏依赖于新工具链文件和旧版工具链文件之间行为差异的构建,不幸的是,将选项重命名以明确“旧版”实际上是推荐的,也会破坏使用该选项的用户。如果您正在愉快地使用新工具链文件,则无需迁移,但请知道,针对新工具链文件行为提出的任何错误可能不会得到修复,相反,您需要迁移。
用法
Gradle
在使用 externalNativeBuild
时,CMake 工具链文件的用法是自动的。有关更多信息,请参阅 Android Studio 的 将 C 和 C++ 代码添加到您的项目 指南。
命令行
在 Gradle 之外使用 CMake 构建时,必须将工具链文件本身及其参数传递给 CMake。例如
$ cmake \
-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=$ABI \
-DANDROID_PLATFORM=android-$MINSDKVERSION \
$OTHER_ARGS
工具链参数
以下参数可以传递到 CMake 工具链文件。如果使用 Gradle 构建,请将参数添加到 android.defaultConfig.externalNativeBuild.cmake.arguments
中,如 ExternalNativeBuild 文档 所述。如果从命令行构建,请使用 -D
将参数传递到 CMake。例如,要强制 armeabi-v7a 不使用 Neon 支持进行构建,请传递 -DANDROID_ARM_NEON=FALSE
。
ANDROID_ABI
目标 ABI。有关支持的 ABI 的信息,请参阅 Android ABI。
Gradle
Gradle 会自动提供此参数。请勿在 build.gradle
文件中显式设置此参数。要控制 Gradle 的目标 ABI,请使用 abiFilters
,如 Android ABI 所述。
命令行
CMake 每次构建针对一个目标。要针对多个 Android ABI,您必须针对每个 ABI 构建一次。建议对每个 ABI 使用不同的构建目录,以避免构建之间的冲突。
值 | 备注 |
---|---|
armeabi-v7a |
|
带有 NEON 的 armeabi-v7a |
与 armeabi-v7a 相同。 |
arm64-v8a |
|
x86 |
|
x86_64 |
ANDROID_ARM_MODE
指定是否为 armeabi-v7a 生成 arm 或 thumb 指令。对其他 ABI 无效。有关更多信息,请参阅 Android ABI 文档。
值 | 备注 |
---|---|
arm | |
thumb | 默认行为。 |
ANDROID_NATIVE_API_LEVEL
ANDROID_PLATFORM 的别名。
ANDROID_PLATFORM
指定应用程序或库支持的最低 API 级别。此值对应于应用程序的 minSdkVersion
。
Gradle
使用 Android Gradle 插件时,此值将自动设置为与应用程序的 minSdkVersion
匹配,无需手动设置。
命令行
直接调用 CMake 时,此值的默认值为正在使用的 NDK 支持的最低 API 级别。例如,使用 NDK r20 时,此值的默认值为 API 级别 16。
此参数接受多种格式
android-$API_LEVEL
$API_LEVEL
android-$API_LETTER
$API_LETTER
格式允许您指定 android-N
,而无需确定与该版本关联的数字。请注意,某些版本在没有字母增加的情况下获得了 API 增加。这些 API 可以通过添加 -MR1
后缀来指定。例如,API 级别 25 是 android-N-MR1
。
ANDROID_STL
指定要用于此应用程序的 STL。有关更多信息,请参阅 C++ 库支持。默认情况下,将使用 c++_static
。
值 | 备注 |
---|---|
c++_shared | libc++ 的共享库变体。 |
c++_static | libc++ 的静态库变体。 |
none | 没有 C++ 标准库支持。 |
system | 系统 STL |
管理编译器标志
如果您需要将特定标志传递到编译器或链接器以进行构建,请参考 CMake 文档以了解 set_target_compile_options 及其相关的选项系列。该页面底部的“另请参阅”部分提供了一些有用的线索。
通常,最佳做法是将编译器标志应用于最窄的可用范围。您要应用于所有目标的标志(例如 -Werror
)在每个模块重复时会很不方便,但仍然很少应该在全局范围内应用 (CMAKE_CXX_FLAGS
),因为它们可能会对项目中的第三方依赖项产生不希望有的影响。对于此类情况,可以在目录范围内应用标志 (add_compile_options
)。
对于编译器标志的狭窄子集,它们也可以在您的 build.gradle 文件中使用 cppFlags
或类似属性进行设置。您不应该这样做。从 Gradle 传递到 CMake 的标志将具有令人惊讶的优先级行为,在某些情况下会覆盖实现隐式传递的标志,这些标志是构建 Android 代码所必需的。始终优先处理直接在 CMake 中处理 CMake 行为。如果您需要根据 AGP buildType
控制编译器标志,请参阅 在 CMake 中使用 AGP 构建类型。
在 CMake 中使用 AGP 构建类型
如果您需要根据自定义 Gradle buildType
调整 CMake 行为,请使用该构建类型传递一个额外的 CMake 标志(而不是编译器标志),您的 CMake 构建脚本可以读取该标志。例如,如果您有“免费”和“高级”构建变体,它们由您的 build.gradle.kts 控制,并且您需要将该数据传递到 CMake
android {
buildTypes {
free {
externalNativeBuild {
cmake {
arguments.add("-DPRODUCT_VARIANT_PREMIUM=OFF")
}
}
}
premium {
externalNativeBuild {
cmake {
arguments.add("-DPRODUCT_VARIANT_PREMIUM=ON")
}
}
}
}
}
然后,在您的 CMakeLists.txt 中
if (DPRODUCT_VARIANT_PREMIUM)
# Do stuff for the premium build.
else()
# Do stuff for the free build.
endif()
变量的名称由您决定,但请确保避免使用任何带有 ANDROID_
、APP_
或 CMAKE_
前缀的内容,以避免与现有标志发生冲突或混淆。
请参阅 Sanitizers NDK 示例,了解示例。
了解 CMake 构建命令
在调试 CMake 构建问题时,了解 Gradle 在交叉编译到 Android 时使用的特定构建参数非常有用。
Android Gradle 插件会将它用于执行针对每个 ABI 和 构建类型 对的 CMake 构建的构建参数保存到 build_command.txt
中。这些文件位于以下目录
<project-root>/<module-root>/.cxx/cmake/<build-type>/<ABI>/
以下片段显示了针对 armeabi-v7a
架构构建 hello-jni
示例的可调试版本的 CMake 参数。
Executable : ${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/cmake
arguments :
-H${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/src/main/cpp
-DCMAKE_FIND_ROOT_PATH=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/prefab/armeabi-v7a/prefab
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_TOOLCHAIN_FILE=${HOME}/Android/Sdk/ndk/22.1.7171670/build/cmake/android.toolchain.cmake
-DANDROID_ABI=armeabi-v7a
-DANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DANDROID_PLATFORM=android-23
-DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a
-DCMAKE_ANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_MAKE_PROGRAM=${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/ninja
-DCMAKE_SYSTEM_NAME=Android
-DCMAKE_SYSTEM_VERSION=23
-B${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/armeabi-v7a
-GNinja
jvmArgs :
Build command args: []
Version: 1
使用预构建库
如果您需要导入的预构建库以 AAR 的形式分发,请遵循 Studio 的依赖项文档 来导入和使用这些库。如果您没有使用 AGP,则可以遵循 https://google.github.io/prefab/example-workflow.html,但迁移到 AGP 可能更容易。
对于没有以 AAR 形式分发的库,有关在 CMake 中使用预构建库的说明,请参阅 CMake 手册中关于 IMPORTED
目标的 add_library
文档。
构建第三方代码
有几种方法可以将第三方代码作为 CMake 项目的一部分进行构建,哪种方法最有效取决于您的情况。最佳选择通常是根本不这样做。相反,构建一个 AAR 用于该库,并在您的应用程序中使用它。您不一定需要发布该 AAR。它可以是您 Gradle 项目的内部库。
如果这不是一个选择
- 将第三方源代码(即复制)到您的存储库中,并使用 add_subdirectory 来构建它。这只有在另一个库也是使用 CMake 构建的情况下才有效。
- 定义一个 ExternalProject。
- 分别构建该库,然后遵循 使用预构建库 将其作为预构建导入。
CMake 中的 YASM 支持
NDK 提供了 CMake 支持,用于构建使用 YASM 编写的汇编代码,以便在 x86 和 x86-64 架构上运行。YASM 是一款基于 NASM 汇编器的开源 x86 和 x86-64 架构汇编器。
要使用 CMake 构建汇编代码,请在项目的 CMakeLists.txt
中进行以下更改
- 使用
enable_language
,并将值设置为ASM_NASM
。 - 根据您构建的是共享库还是可执行二进制文件,请调用
add_library
或add_executable
。在参数中,传递一个源文件列表,该列表包含 YASM 中汇编程序的.asm
文件以及关联的 C 库或函数的.c
文件。
以下片段展示了如何配置 CMakeLists.txt
以将 YASM 程序构建为共享库。
cmake_minimum_required(VERSION 3.6.0)
enable_language(ASM_NASM)
add_library(test-yasm SHARED jni/test-yasm.c jni/print_hello.asm)
有关如何将 YASM 程序构建为可执行文件的示例,请参阅 NDK git 存储库中的 yasm 测试。
报告问题
如果您在使用 NDK 或其 CMake 工具链文件时遇到任何问题,请通过 GitHub 上的 android-ndk/ndk 问题跟踪器报告这些问题。对于 Gradle 或 Android Gradle 插件问题,请报告 Studio 错误。