Android Studio 如何配置 CMake

前面的小节我们学习了如何安装 NDK 和 CMake。本小节学习如何配置 CMake。

1. 概述

CMake 构建脚本是一个纯文本文件,我们必须将其命名为 CMakeLists.txt,并在其中包含 CMake 构建我们的 C/C++ 库时需要使用的命令。如果我们的原生源代码文件还没有 CMake 构建脚本,我们需要自行创建一个,并在其中包含适当的 CMake 命令。

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

2. 创建 CMake 构建脚本

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

  1. IDE 的左侧打开 Project 窗格,然后从下拉菜单中选择 Project 视图。

  2. 右键点击 your-module 的根目录,然后依次选择 New > File

  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 )

在使用 add_library() 向 CMake 构建脚本添加源代码文件或库时,Android Studio 还会在我们同步项目后在 Project 视图中显示相关的头文件。不过,为了让 CMake 能够在编译时找到头文件,我们需要向 CMake 构建脚本添加 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 代码中加载此库时,要使用在 CMake 构建脚本中指定的名称:

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

3. 添加 NDK API

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

Android 平台上已存在预构建的 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} )

4. 添加其他预构建库

添加预构建库的步骤与为 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/ )

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

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

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

5. 包含其他 CMake 项目

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

# 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 )

6. 小结

本节课程我们主要学习了如何配置 CMake。本节课程的重点如下:

  • 掌握如何创建 CMake 构建脚本;
  • 掌握如何添加 NDK API 和 预构建库。