1. 程式人生 > >[Android Studio] 向您的專案新增 C 和 C++ 程式碼

[Android Studio] 向您的專案新增 C 和 C++ 程式碼

搭配使用 Android Studio 2.2 或更高版本與 Android Plugin for Gradle 版本 2.2.0 或更高版本時,您可以將 C 和 C++ 程式碼編譯到 Gradle 與 APK 一起打包的原生庫中,將這類程式碼新增到您的應用中。您的 Java 程式碼隨後可以通過 Java 原生介面 (JNI) 呼叫您的原生庫中的函式。如果您想要詳細瞭解如何使用 JNI 框架,請閱讀 Android 的 JNI 提示

Android Studio 用於構建原生庫的預設工具是 CMake。由於很多現有專案都使用構建工具包編譯其原生程式碼,Android Studio 還支援 

ndk-build。如果您想要將現有的 ndk-build 庫匯入到您的 Android Studio 專案中,請參閱介紹如何配置 Gradle 以關聯到您的原生庫的部分。不過,如果您在建立新的原生庫,則應使用 CMake。

本頁面介紹的資訊可以幫助您使用所需構建工具設定 Android Studio、建立或配置專案以支援 Android 上的原生程式碼,以及構建和執行應用。

:如果您的現有專案使用已棄用的 ndkCompile 工具,則應先開啟 build.properties 檔案,並移除以下程式碼行,然後再將 Gradle 關聯到您的原生庫

// Remove this line
android.useDeprecatedNdk =true

實驗性 Gradle 的使用者注意事項:如果您是以下任意一種情況,請考慮遷移到外掛版本 2.2.0 或更高版本並使用 CMake 或 ndk-build 構建原生庫:您的原生專案已經使用 CMake 或者 ndk-build;但是您想要使用穩定版本的 Gradle 構建系統;或者您希望支援外掛工具,例如 CCache。否則,您可以繼續使用實驗性版本的 Gradle 和 Android 外掛

下載 NDK 和構建工具

要為您的應用編譯和除錯原生程式碼,您需要以下元件:

  • :這套工具集允許您為 Android 使用 C 和 C++ 程式碼,並提供眾多平臺庫,讓您可以管理原生 Activity 和訪問物理裝置元件,例如感測器和觸控輸入。
  • CMake:一款外部構建工具,可與 Gradle 搭配使用來構建原生庫。如果您只計劃使用 ndk-build,則不需要此元件。
  • LLDB:一種除錯程式,Android Studio 使用它來除錯原生程式碼
  1. 在開啟的專案中,從選單欄選擇 Tools > Android > SDK Manager
  2. 點選 SDK Tools 標籤。
  3. 選中 LLDBCMake 和 NDK 旁的複選框,如圖 1 所示。

    圖 1. 從 SDK 管理器中安裝 LLDB、CMake 和 NDK。

  4. 點選 Apply,然後在彈出式對話方塊中點選 OK
  5. 安裝完成後,點選 Finish,然後點選 OK

建立支援 C/C++ 的新專案

  1. 在嚮導的 Configure your new project 部分,選中 Include C++ Support 複選框。
  2. 點選 Next
  3. 正常填寫所有其他欄位並完成嚮導接下來的幾個部分。
  4. 在嚮導的 Customize C++ Support 部分,您可以使用下列選項自定義專案:
    • C++ Standard:使用下拉列表選擇您希望使用哪種 C++ 標準。選擇 Toolchain Default 會使用預設的 CMake 設定。
    • Exceptions Support:如果您希望啟用對 C++ 異常處理的支援,請選中此複選框。如果啟用此複選框,Android Studio 會將 -fexceptions 標誌新增到模組級 build.gradle 檔案的 cppFlags 中,Gradle 會將其傳遞到 CMake。
    • Runtime Type Information Support:如果您希望支援 RTTI,請選中此複選框。如果啟用此複選框,Android Studio 會將 -frtti 標誌新增到模組級 build.gradle 檔案的 cppFlags 中,Gradle 會將其傳遞到 CMake。
  5. 點選 Finish

在 Android Studio 完成新專案的建立後,請從 IDE 左側開啟 Project 窗格並選擇 Android 檢視。如圖 2 中所示,Android Studio 將新增 cpp 和 External Build Files 組:

圖 2. 您的原生原始檔和外部構建指令碼的 Android 檢視組。

:此檢視無法反映磁碟上的實際檔案層次結構,而是將相似檔案分到一組中,簡化專案導航。

在 cpp 組中,您可以找到屬於專案的所有原生原始檔、標頭和預構建庫。對於新專案,Android Studio 會建立一個示例 C++ 原始檔 native-lib.cpp,並將其置於應用模組的 src/main/cpp/ 目錄中。本示例程式碼提供了一個簡單的 C++ 函式 stringFromJNI(),此函式可以返回字串“Hello from C++”。要了解如何向專案新增其他原始檔,請參閱介紹如何建立新的原生原始檔的部分。在 External Build Files 組中,您可以找到 CMake 或 ndk-build 的構建指令碼。與 build.gradle 檔案指示 Gradle 如何構建應用一樣,CMake 和 ndk-build 需要一個構建指令碼來了解如何構建您的原生庫。對於新專案,Android Studio 會建立一個 CMake 構建指令碼 CMakeLists.txt,並將其置於模組的根目錄中。要詳細瞭解此構建指令碼的內容,請參閱介紹如何建立 Cmake 構建指令碼的部分。

構建和執行示例應用

點選 Run 從選單欄執行應用 後,Android Studio 將在您的 Android 裝置或者模擬器上構建並啟動一個顯示文字“Hello from C++”的應用。下面的概覽介紹了構建和執行示例應用時會發生的事件:

  1. Gradle 呼叫您的外部構建指令碼 CMakeLists.txt
  2. CMake 按照構建指令碼中的命令將 C++ 原始檔 native-lib.cpp 編譯到共享的物件庫中,並命名為 libnative-lib.so,Gradle 隨後會將其打包到 APK 中。
  3. 執行時,應用的 MainActivity 會使用 System.loadLibrary() 載入原生庫。現在,應用可以使用庫的原生函式 stringFromJNI()
  4. MainActivity.onCreate() 呼叫 stringFromJNI(),這將返回“Hello from C++”並使用這些文字更新 TextView

Instant Run 與使用原生程式碼的專案不相容。Android Studio 會自動停用此功能。

如果您想要驗證 Gradle 是否已將原生庫打包到 APK 中,可以使用 APK 分析器

  1. 選擇 Build > Analyze APK
  2. 從 app/build/outputs/apk/ 目錄中選擇 APK 並點選 OK
  3. 如圖 3 中所示,您會在 APK 分析器視窗的 lib/<ABI>/ 下看到 libnative-lib.so

    圖 3. 使用 APK 分析器定位原生庫。

提示:如果您想要試驗使用原生程式碼的其他 Android 應用,請點選 File > New > Import Sample 並從 Ndk 列表中選擇示例專案。

向現有專案新增 C/C++ 程式碼

如果您希望向現有專案新增原生程式碼,請執行以下步驟:

  1. 建立新的原生原始檔並將其新增到您的 Android Studio 專案中。
    • 如果您已經擁有原生程式碼或想要匯入預構建的原生庫,則可以跳過此步驟。
  2. 建立 CMake 構建指令碼,將您的原生原始碼構建到庫中。如果匯入和關聯預構建庫或平臺庫,您也需要此構建指令碼。
    • 如果您的現有原生庫已經擁有 CMakeLists.txt 構建指令碼或者使用 ndk-build 幷包含 Android.mk 構建指令碼,則可以跳過此步驟。
  3. 提供一個指向您的 CMake 或 ndk-build 指令碼檔案的路徑,將 Gradle 關聯到您的原生庫。Gradle 使用構建指令碼將原始碼匯入您的 Android Studio 專案並將原生庫(SO 檔案)打包到 APK 中。

配置完專案後,您可以使用 JNI 框架從 Java 程式碼中訪問您的原生函式。要構建和執行應用,只需點選 Run 從選單欄執行應用。Gradle 會以依賴項的形式新增您的外部原生構建流程,用於編譯、構建原生庫並將其隨 APK 一起打包。

建立新的原生原始檔

要在應用模組的主原始碼集中建立一個包含新建原生原始檔的 cpp/ 目錄,請按以下步驟操作:

  1. 從 IDE 的左側開啟 Project 窗格並從下拉選單中選擇 Project 檢視。
  2. 導航到 您的模組 > src,右鍵點選 main 目錄,然後選擇 New > Directory
  3. 為目錄輸入一個名稱(例如 cpp)並點選 OK
  4. 右鍵點選您剛剛建立的目錄,然後選擇 New > C/C++ Source File
  5. 為您的原始檔輸入一個名稱,例如 native-lib
  6. 從 Type 下拉選單中,為您的原始檔選擇副檔名,例如 .cpp
    • 點選 Edit File Types ,您可以向下拉選單中新增其他檔案型別,例如 .cxx 或 .hxx。在彈出的 C/C++ 對話方塊中,從 Source Extension 和 Header Extension 下拉選單中選擇另一個副檔名,然後點選 OK
  7. 如果您還希望建立一個標標頭檔案,請選中 Create an associated header 複選框。
  8. 點選 OK

建立 CMake 構建指令碼

如果您的原生原始檔還沒有 CMake 構建指令碼,則您需要自行建立一個幷包含適當的 CMake 命令。CMake 構建指令碼是一個純文字檔案,您必須將其命名為 CMakeLists.txt。本部分介紹了您應包含到構建指令碼中的一些基本命令,用於在建立原生庫時指示 CMake 應使用哪些原始檔。

:如果您的專案使用 ndk-build,則不需要建立 CMake 構建指令碼。提供一個指向您的 Android.mk 檔案的路徑,將 Gradle 關聯到您的原生庫

要建立一個可以用作 CMake 構建指令碼的純文字檔案,請按以下步驟操作:

  1. 從 IDE 的左側開啟 Project 窗格並從下拉選單中選擇 Project 檢視。
  2. 右鍵點選 您的模組 的根目錄並選擇 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 可以在編譯時定位您的標標頭檔案,您需要將 include_directories() 命令新增到 CMake 構建指令碼中並指定標頭的路徑:

add_library(...)

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

CMake 使用以下規範來為庫檔案命名:

lib庫名稱.so

例如,如果您在構建指令碼中指定“native-lib”作為共享庫的名稱,CMake 將建立一個名稱為 libnative-lib.so 的檔案。不過,在 Java 程式碼中載入此庫時,請使用您在 CMake 構建指令碼中指定的名稱:

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

:如果您在 CMake 構建指令碼中重新命名或移除某個庫,您需要先清理專案,Gradle 隨後才會應用更改或者從 APK 中移除舊版本的庫。要清理專案,請從選單欄中選擇 Build > Clean Project

Android Studio 會自動將原始檔和標頭新增到 Project 窗格的 cpp 組中。使用多個 add_library() 命令,您可以為 CMake 定義要從其他原始檔構建的更多庫。

新增 NDK API

Android NDK 提供了一套實用的原生 API 和庫。通過將 NDK 庫包含到專案的 CMakeLists.txt 指令碼檔案中,您可以使用這些 API 中的任意一種。

預構建的 NDK 庫已經存在於 Android 平臺上,因此,您無需再構建或將其打包到 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 )

某些庫為特定的 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} )

在您構建應用時,Gradle 會自動將匯入的庫打包到 APK 中。您可以使用 APK 分析器驗證 Gradle 將哪些庫打包到您的 APK 中。如需瞭解有關 CMake 命令的詳細資訊,請參閱 CMake 文件

要將 Gradle 關聯到您的原生庫,您需要提供一個指向 CMake 或 ndk-build 指令碼檔案的路徑。在您構建應用時,Gradle 會以依賴項的形式執行 CMake 或 ndk-build,並將共享的庫打包到您的 APK 中。Gradle 還使用構建指令碼來了解要將哪些檔案新增到您的 Android Studio 專案中,以便您可以從 Project 視窗訪問這些檔案。如果您的原生原始檔沒有構建指令碼,則需要先建立 CMake 構建指令碼,然後再繼續。

將 Gradle 關聯到原生專案後,Android Studio 會更新 Project 窗格以在 cpp 組中顯示您的原始檔和原生庫,在 External Build Files 組中顯示您的外部構建指令碼。

:更改 Gradle 配置時,請確保通過點選工具欄中的 Sync Project  應用更改。此外,如果在將 CMake 或 ndk-build 指令碼檔案關聯到 Gradle 後再對其進行更改,您應當從選單欄中選擇 Build > Refresh Linked C++ Projects,將 Android Studio 與您的更改同步。

您可以使用 Android Studio UI 將 Gradle 關聯到外部 CMake 或 ndk-build 專案:

  1. 從 IDE 左側開啟 Project 窗格並選擇 Android 檢視。
  2. 右鍵點選您想要關聯到原生庫的模組(例如 app 模組),並從選單中選擇 Link C++ Project with Gradle。您應看到一個如圖 4 所示的對話方塊。
  3. 從下拉選單中,選擇 CMake 或 ndk-build
    1. 如果您選擇 CMake,請使用 Project Path 旁的欄位為您的外部 CMake 專案指定 CMakeLists.txt 指令碼檔案。
    2. 如果您選擇 ndk-build,請使用 Project Path 旁的欄位為您的外部 ndk-build 專案指定 Android.mk 指令碼檔案。如果 Application.mk 檔案與您的 Android.mk 檔案位於相同目錄下,Android Studio 也會包含此檔案。

    圖 4. 使用 Android Studio 對話方塊關聯外部 C++ 專案。

  4. 點選 OK

手動配置 Gradle

要手動配置 Gradle 以關聯到您的原生庫,您需要將 externalNativeBuild {} 塊新增到模組級 build.gradle 檔案中,並使用 cmake {} 或 ndkBuild {}對其進行配置:

android {...
  defaultConfig {...}
  buildTypes {...}// Encapsulates your external native build configurations.
  externalNativeBuild {// Encapsulates your CMake build configurations.
    cmake {// Provides a relative path to your CMake build script.
      path "CMakeLists.txt"}}}

:如果您想要將 Gradle 關聯到現有 ndk-build 專案,請使用 ndkBuild {} 塊而不是 cmake {},並提供 Android.mk 檔案的相對路徑。如果 Application.mk 檔案與您的 Android.mk 檔案位於相同目錄下,Gradle 也會包含此檔案。

指定可選配置

您可以在模組級 build.gradle 檔案的 defaultConfig {} 塊中配置另一個 externalNativeBuild {} 塊,為 CMake 或 ndk-build 指定可選引數和標誌。與 defaultConfig {} 塊中的其他屬性類似,您也可以在構建配置中為每個產品風味重寫這些屬性。

例如,如果您的 CMake 或 ndk-build 專案定義多個原生庫,您可以使用 targets 屬性僅為給定產品風味構建和打包這些庫中的一部分。以下程式碼示例說明了您可以配置的部分屬性:

android {...
  defaultConfig {...// This block is different from the one you use to link Gradle// to your CMake or ndk-build script.
    externalNativeBuild {// For ndk-build, instead use ndkBuild {}
      cmake {// Passes optional arguments to CMake.
        arguments "-DANDROID_ARM_NEON=TRUE","-DANDROID_TOOLCHAIN=clang"// Sets optional flags for the C compiler.
        cFlags "-D_EXAMPLE_C_FLAG1","-D_EXAMPLE_C_FLAG2"// Sets a flag to enable format macro constants for the C++ compiler.
        cppFlags "-D__STDC_FORMAT_MACROS"}}}

  buildTypes {...}

  productFlavors {...
    demo {...
      externalNativeBuild {
        cmake {...// Specifies which native libraries to build and package for this// product flavor. If you don't configure this property, Gradle// builds and packages all shared object libraries that you define// in your CMake or ndk-build project.
          targets "native-lib-demo"}}}

    paid {...
      externalNativeBuild {
        cmake {...
          targets "native-lib-paid"}}}}// Use this block to link Gradle to your CMake or ndk-build script.
  externalNativeBuild {
    cmake {...}// or ndkBuild {...}}}

要詳細瞭解配置產品風味和構建變體,請參閱配置構建變體。如需瞭解您可以使用 arguments 屬性為 CMake 配置的變數列表,請參閱使用 CMake 變數

指定 ABI

預設情況下,Gradle 會針對 NDK 支援的 ABI 將您的原生庫構建到單獨的 .so 檔案中,並將其全部打包到您的 APK 中。如果您希望 Gradle 僅構建和打包原生庫的特定 ABI 配置,您可以在模組級 build.gradle 檔案中使用 ndk.abiFilters 標誌指定這些配置,如下所示:

android {...
  defaultConfig {...
    externalNativeBuild {
      cmake {...}// or ndkBuild {...}}

    ndk {// Specifies the ABI configurations of your native// libraries Gradle should build and package with your APK.
      abiFilters 'x86','x86_64','armeabi','armeabi-v7a','arm64-v8a'}}
  buildTypes {...}
  externalNativeBuild {...}}

在大多數情況下,您只需要在 ndk {} 塊中指定 abiFilters(如上所示),因為它會指示 Gradle 構建和打包原生庫的這些版本。不過,如果您希望控制 Gradle 應當構建的配置,並獨立於您希望其打包到 APK 中的配置,請在 defaultConfig.externalNativeBuild.cmake {} 塊(或 defaultConfig.externalNativeBuild.ndkBuild {} 塊中)配置另一個 abiFilters 標誌。Gradle 會構建這些 ABI 配置,不過僅會打包您在 defaultConfig.ndk{} 塊中指定的配置。

為了進一步降低 APK 的大小,請考慮配置 ABI APK 拆分,而不是建立一個包含原生庫所有版本的大型 APK,Gradle 會為您想要支援的每個 ABI 建立單獨的 APK,並且僅打包每個 ABI 需要的檔案。如果您配置 ABI 拆分,但沒有像上面的程式碼示例一樣指定 abiFilters 標誌,Gradle 會構建原生庫的所有受支援 ABI 版本,不過僅會打包您在 ABI 拆分配置中指定的版本。為了避免構建您不想要的原生庫版本,請為 abiFilters 標誌和 ABI 拆分配置提供相同的 ABI 列表。