The Android Developer Challenge is back! Submit your idea before December 2.

配置 CMake

CMake 编译脚本是一个纯文本文件,您必须将其命名为 CMakeLists.txt,并在其中包含 CMake 编译您的 C/C++ 库时需要使用的命令。如果原生源代码文件还没有 CMake 编译脚本,您需要自行创建一个,并在其中包含适当的 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

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

  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 编译文件添加 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 编译脚本中指定的名称:

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

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

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

添加 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} )
    

添加其他预编译库

添加预编译库的步骤与为 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 项目中包含它们的输出,您可以使用一个 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 )