設定 CMake

CMake 建構指令碼是純文字檔案,必須命名為 CMakeLists.txt,並包含 CMake 用來建構 C/C++ 程式庫的指令。如果原生來源不含 CMake 建構指令碼,您則須自行建立並加入適當的 CMake 指令。如要瞭解如何安裝 CMake,請參閱「安裝並設定 NDK 和 CMake」。

本節將介紹您應該在建構指令碼中加入哪些基本指令,好對 CMake 告知在建立原生程式庫時要使用的來源。詳情請參閱 CMake 指令的官方說明文件。

設定新的 CMake 建構指令碼後,您必須設定 Gradle,將 CMake 專案做為建構依附元件納入其中,這樣 Gradle 才能使用應用程式的 APK 來建構及封裝您的原生程式庫。

注意:如果專案使用的是 ndk-build,則不需要建立 CMake 建構指令碼。您只需要提供 Android.mk 檔案路徑,就能設定 Gradle,納入現有的原生程式庫專案。

建立 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 建構指令碼中指定的名稱:

Kotlin

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

Java

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 提供所需程式庫的名稱,並將該程式庫連結至您的原生程式庫即可。

find_library() 指令加入 CMake 建構指令碼,就能找出 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_NDK 路徑變數,Android Studio 會自動為您定義這個變數。

下列指令會指示 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 檔案做為頂層 CMake 建構指令碼 (該檔案會與 Gradle 連結),然後新增其他 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 )

透過指令列呼叫 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 檔案)。您必須使用 CMAKE_TOOLCHAIN_FILE,才能運用 NDK 的 CMake 支援功能。如果是 CMake 3.21 以上版本,可以改用 CMake 內建的 NDK 支援功能,但必須根據 CMake 的 Android 跨平台程式碼編譯說明文件,使用不同的變數群組。