天天看點

在Android Studio中建立能調用C/C++函數的APK

這段時間開始是用純 Java 語言開發過amazon 的 Alexa Voice Service(AVS)用戶端, 後來考慮到程式的效率以及後續需要适配非anroid 平台的話,我們需要将核心應用用 C/C++ 開發,是以就涉及到了混合 Java 、C/C++ 開發 Android APK的場景, 網上找了一堆, 大家說的很多是基于老的elipse的或者android studio的,而且很多與自己的特定項目曆史有關, 後來在用AS建立一個支援 C/C++的工程的 cmakelist檔案,在檔案中提到一個網頁,就找到那個網頁,順便找到這個權威的官方教程了。

我隻是一個搬運工, 參考android 幫助文檔:https://developer.android.com/studio/projects/add-native-code.html#download-ndk

搭配使用 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 原生開發工具包 (NDK):這套工具集允許您為 Android 使用 C 和 C++ 代碼,并提供衆多平台庫,讓您可以管理原生 Activity 和通路實體裝置元件,例如傳感器和觸摸輸入。
  • CMake:一款外部建構工具,可與 Gradle 搭配使用來建構原生庫。如果您隻計劃使用 ndk-build,則不需要此元件。
  • LLDB:一種調試程式,Android Studio 使用它來調試原生代碼。

您可以使用 SDK 管理器安裝這些元件:

  1. 在打開的項目中,從菜單欄選擇 Tools > Android > SDK Manager。
  2. 點選 SDK Tools 标簽。
  3. 選中 LLDB、CMake 和 NDK 旁的複選框,如圖 1 所示。
    在Android Studio中建立能調用C/C++函數的APK
    圖 1. 從 SDK 管理器中安裝 LLDB、CMake 和 NDK。
  4. 點選 Apply,然後在彈出式對話框中點選 OK。
  5. 安裝完成後,點選 Finish,然後點選 OK。

建立支援 C/C++ 的新項目

建立支援原生代碼的項目與建立任何其他 Android Studio 項目類似,不過前者還需要額外幾個步驟:

  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 組:

  1. 在Android Studio中建立能調用C/C++函數的APK

    圖 2. 您的原生源檔案和外部建構腳本的 Android 視圖組。

    注:此視圖無法反映磁盤上的實際檔案層次結構,而是将相似檔案分到一組中,簡化項目導航。

  2. 在 cpp 組中,您可以找到屬于項目的所有原生源檔案、标頭和預建構庫。對于新項目,Android Studio 會建立一個示例 C++ 源檔案

    native-lib.cpp

    ,并将其置于應用子產品的

    src/main/cpp/

    目錄中。本示例代碼提供了一個簡單的 C++ 函數

    stringFromJNI()

    ,此函數可以傳回字元串“Hello from C++”。要了解如何向項目添加其他源檔案,請參閱介紹如何建立新的原生源檔案的部分。
  3. 在 External Build Files 組中,您可以找到 CMake 或 ndk-build 的建構腳本。與

    build.gradle

    檔案訓示 Gradle 如何建構應用一樣,CMake 和 ndk-build 需要一個建構腳本來了解如何建構您的原生庫。對于新項目,Android Studio 會建立一個 CMake 建構腳本

    CMakeLists.txt

    ,并将其置于子產品的根目錄中。要詳細了解此建構腳本的内容,請參閱介紹如何建立 Cmake 建構腳本的部分。

建構和運作示例應用

點選 Run

在Android Studio中建立能調用C/C++函數的APK

後,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

    在Android Studio中建立能調用C/C++函數的APK
    圖 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

在Android Studio中建立能調用C/C++函數的APK

。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
      在Android Studio中建立能調用C/C++函數的APK
      ,您可以向下拉菜單中添加其他檔案類型,例如

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

然後,您需要使用

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

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

将 Gradle 關聯到您的原生庫

要将 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

在Android Studio中建立能調用C/C++函數的APK

應用更改。此外,如果在将 CMake 或 ndk-build 腳本檔案關聯到 Gradle 後再對其進行更改,您應當從菜單欄中選擇 Build > Refresh Linked C++ Projects,将 Android Studio 與您的更改同步。

使用 Android Studio UI

您可以使用 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 也會包含此檔案。
    在Android Studio中建立能調用C/C++函數的APK
    圖 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 清單。

繼續閱讀