配置 CMake

CMake 构建脚本是一个纯文本文件,您必须将其命名为 CMakeLists.txt,并在其中包含 CMake 构建您的 C/C++ 库时需要使用的命令。如果您的原生源代码文件还没有 CMake 构建脚本,您需要自行创建一个,并在其中包含适当的 CMake 命令。如需了解如何安装 CMake,请参阅安装及配置 NDK 和 CMake

本部分将介绍您应该在构建脚本中包含哪些基本命令,以便指示 CMake 在创建原生库时使用哪些源代码文件。如需了解详情,请参阅介绍 CMake 命令的官方文档。

在配置新的 CMake 构建脚本后,您需要配置 Gradle 以将 CMake 项目作为构建依赖项包含在内,从而让 Gradle 构建原生库,并将其与应用的 APK 打包在一起。

注意:如果项目使用的是 ndk-build,您不需要创建 CMake 构建脚本。您只需配置 Gradle 以包含现有的原生库项目即可;为此,您需要提供 Android.mk 文件的路径。

创建 CMake 构建脚本

如需创建一个可以用作 CMake 构建脚本的纯文本文件,请按以下步骤操作:

  1. 从 IDE 的左侧打开 Project 窗格,然后从下拉菜单中选择 Project 视图。
  2. 右键点击 your-module 的根目录,然后依次选择 New > File

    注意:您可以在所需的任何位置创建构建脚本。不过,在配置 build 脚本时,原生源代码文件和库的路径将与 build 脚本的位置相关。

  3. 输入“CMakeLists.txt”作为文件名,然后点击 OK

现在,您可以通过添加 CMake 命令来配置您的构建脚本。如需指示 CMake 根据原生源代码创建原生库,请向您的构建脚本添加 cmake_minimum_required()add_library() 命令:

# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.

cmake_minimum_required(VERSION 3.4.1)

# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add_library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.

add_library( # Specifies the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

提示:与指示 CMake 根据源代码文件创建原生库的方式一样,您可以使用 add_executable() 命令指示 CMake 改为根据这些源代码文件来创建可执行文件。不过,根据原生源代码文件构建可执行文件是可选操作,构建原生库以将其打包到 APK 中即可满足大多数项目的要求。

在使用 add_library() 向 CMake 构建脚本添加源代码文件或库时,Android Studio 还会在您同步项目后在 Project 视图中显示相关的头文件。不过,为了让 CMake 能够在编译时找到头文件,您需要向 CMake build 脚本添加 include_directories() 命令,并指定头文件的路径:

add_library(...)

# Specifies a path to native header files.
include_directories(src/main/cpp/include/)

CMake 使用以下规范来为库文件命名:

liblibrary-name.so

例如,如果您在构建脚本中指定“native-lib”作为共享库的名称,CMake 就会创建一个名为 libnative-lib.so 的文件。不过,在 Java 或 Kotlin 代码中加载此库时,请使用您在 CMake build 脚本中指定的名称:

Kotlin

companion object {
    init {
        System.loadLibrary("native-lib");
    }
}

Java

static {
    System.loadLibrary("native-lib");
}

注意:如果重命名或移除了 CMake build 脚本中的库,您需要在 Gradle 应用相关更改或从 APK 中移除旧版库之前清理您的项目。如需清理项目,请在菜单栏中依次选择 Build > Clean Project

Android Studio 会自动向 Project 窗格中的 cpp 组添加源代码文件和头文件。通过使用多个 add_library() 命令,您可以为 CMake 定义要根据其他源代码文件构建的更多库。

添加 NDK API

Android NDK 提供了一套您可能会觉得非常实用的原生 API 和库。通过在项目的 CMakeLists.txt 脚本文件中包含 NDK 库,您可以使用其中任何 API。

Android 平台上已存在预构建的 NDK 库,因此您无需构建 NDK 库或将其打包到 APK 中。由于这些 NDK 库已位于 CMake 搜索路径中,因此您甚至无需指定本地安装的 NDK 库的位置,您只需为 CMake 提供您想要使用的库的名称,并将其与您自己的原生库相关联即可。

向 CMake 构建脚本添加 find_library() 命令以找到 NDK 库并将其路径存储为一个变量。您可以使用此变量在构建脚本的其他部分引用 NDK 库。以下示例会找到 Android 专有的日志支持库,并将其路径存储在 log-lib 中:

find_library( # Defines the name of the path variable that stores the
              # location of the NDK library.
              log-lib

              # Specifies the name of the NDK library that
              # CMake needs to locate.
              log )

为了让您的原生库能够调用 log 库中的函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令来关联这些库:

find_library(...)

# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the log library to the target library.
                       ${log-lib} )

NDK 还以源代码的形式包含一些库,您需要构建这些代码并将其关联到您的原生库。您可以使用 CMake 构建脚本中的 add_library() 命令将源代码编译到原生库中。如需提供本地 NDK 库的路径,您可以使用 Android Studio 自动为您定义的 ANDROID_NDK 路径变量。

以下命令可以指示 CMake 将 android_native_app_glue.c(负责管理 NativeActivity 生命周期事件和触控输入)构建至静态库,并将其与 native-lib 关联:

add_library( app-glue
             STATIC
             ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )

# You need to link static libraries against your shared native library.
target_link_libraries( native-lib app-glue ${log-lib} )

添加其他预构建库

添加预构建库的步骤与为 CMake 指定其他要构建的原生库的步骤相似。不过,由于库已构建,因此您需要使用 IMPORTED 标志指示 CMake 您只想要将此库导入到您的项目中:

add_library( imported-lib
             SHARED
             IMPORTED )

然后,您需要使用 set_target_properties() 命令指定库的路径,具体命令如下所示。

某些库会针对特定的 CPU 架构或应用二进制接口 (ABI) 提供单独的软件包,并将其整理到单独的目录中。此方法既有助于库充分利用特定的 CPU 架构,又能让您只使用所需的库版本。如需向 CMake 构建脚本添加库的多个 ABI 版本,而不必为库的每个版本编写多个命令,您可以使用 ANDROID_ABI 路径变量。此变量使用的是 NDK 支持的一组默认 ABI,或者您手动配置 Gradle 而让其使用的一组经过过滤的 ABI。例如:

add_library(...)
set_target_properties( # Specifies the target library.
                       imported-lib

                       # Specifies the parameter you want to define.
                       PROPERTIES IMPORTED_LOCATION

                       # Provides the path to the library you want to import.
                       imported-lib/src/${ANDROID_ABI}/libimported-lib.so )

为了让 CMake 能够在编译时找到头文件,您需要使用 include_directories() 命令并包含相应头文件的路径:

include_directories( imported-lib/include/ )

注意:如果您想要打包不属于构建时依赖项的预构建库(例如在添加属于 imported-lib 依赖项的预构建库时),则无需按以下说明执行关联库的操作。

如需将预构建库关联到您自己的原生库,请将其添加到 CMake 构建脚本的 target_link_libraries() 命令中:

target_link_libraries( native-lib imported-lib app-glue ${log-lib} )

如需将预构建库打包到 APK 中,您需要使用 sourceSets手动配置 Gradle 以包含 .so 文件的路径。构建 APK 后,您可以使用 APK 分析器验证 Gradle 会将哪些库打包到您的 APK 中。

包含其他 CMake 项目

如果想要构建多个 CMake 项目并在 Android 项目中包含这些 CMake 项目的输出,您可以使用一个 CMakeLists.txt 文件(即您关联到 Gradle 的那个文件)作为顶级 CMake build 脚本,并添加其他 CMake 项目作为此 build 脚本的依赖项。以下顶级 CMake build 脚本会使用 add_subdirectory() 命令将另一个 CMakeLists.txt 文件指定为 build 依赖项,然后关联其输出,就像处理任何其他预构建库一样。

# Sets lib_src_DIR to the path of the target CMake project.
set( lib_src_DIR ../gmath )

# Sets lib_build_DIR to the path of the desired output directory.
set( lib_build_DIR ../gmath/outputs )
file(MAKE_DIRECTORY ${lib_build_DIR})

# Adds the CMakeLists.txt file located in the specified directory
# as a build dependency.
add_subdirectory( # Specifies the directory of the CMakeLists.txt file.
                  ${lib_src_DIR}

                  # Specifies the directory for the build outputs.
                  ${lib_build_DIR} )

# Adds the output of the additional CMake build as a prebuilt static
# library and names it lib_gmath.
add_library( lib_gmath STATIC IMPORTED )
set_target_properties( lib_gmath PROPERTIES IMPORTED_LOCATION
                       ${lib_build_DIR}/${ANDROID_ABI}/lib_gmath.a )
include_directories( ${lib_src_DIR}/include )

# Links the top-level CMake build output against lib_gmath.
target_link_libraries( native-lib ... lib_gmath )

从命令行调用 CMake

使用以下命令调用 CMake 以在 Android Studio 外生成 Ninja 项目:

cmake
-Hpath/to/cmakelists/folder
-Bpath/to/generated/ninja/project/debug/ABI
-DANDROID_ABI=ABI                               // For example, arm64-v8a
-DANDROID_PLATFORM=platform-version-string      // For example, android-16
-DANDROID_NDK=android-sdk/ndk/ndk-version
-DCMAKE_TOOLCHAIN_FILE=android-sdk/ndk/ndk-version/build/cmake/android.toolchain.cmake
-G Ninja

执行此命令所生成的 Ninja 项目可创建 Android 可执行文件库(.so 文件)。使用 NDK 的 CMake 支持需要使用 CMAKE_TOOLCHAIN_FILE。对于 CMake 3.21 或更高版本,您可以改用 CMake 的内置 NDK 支持,但必须使用 CMake 的 Android 交叉编译文档中介绍的一组不同变量。