1. 程式人生 > >在 Android Studio 2.2 中愉快地使用 C/C++

在 Android Studio 2.2 中愉快地使用 C/C++

注:官網上面的技術文章也在不斷地漢化中,只是進度有點慢。在我翻譯本篇文章的時候,官網沒有對應的中文教程。經人提醒,該文章現在在官網已經有對應的中文版教程了,連結地址:向您的專案新增 C 和 C++ 程式碼。有需要的朋友可以直接閱讀官方中文解說。

———————–原文分割線——————————-

Android Studio 2.2 正式版釋出後,看到更新內容中有提到對 C/C++ 支援的完善,表示非常高興。然後將官網上這一部分內容翻譯出來,如有錯誤,歡迎指正。

使用 Android studio,你可以將 C 和 C++ 程式碼構建成 native library(即 .so 檔案),然後打包到你的 APK 中。你的 Java 程式碼可以通過 Java Native Interface(JNI)呼叫 native library 庫中的方法。

Android Studio 預設使用 CMake 編譯原生庫。由於已經有大量的程式碼使用了 ndk-build 來編譯 native code,所以 Android Studio 同樣也支援 ndk build。如果你想匯入一個 ndk-build 庫到你的 Android Studio 專案中,請參閱後文的 關聯本地庫與 Gradle 部分。然而,如果你建立了一個新的 native 庫工程,你應該使用 CMake。

本篇文章將會說明如何使用 Android Studio 來建立、配置 Android 專案,以支援 native code,以及將其執行到你的 app 中。

注意:要在 Android Studio 中使用 CMake 或者 ndk-build,你需要使用 Android Studio 2.2 或更高的版本,同時需要配合使用 Android Plugin for Gradle 2.2.0 及以上的版本。

下載 NDK 和構建工具

要編譯和除錯原生代碼(native code),你需要下面的元件:

  • The Android Native Development Kit (NDK): 讓你能在 Android 上面使用 C 和 C++ 程式碼的工具集。
  • CMake: 外部構建工具。如果你準備只使用 ndk-build 的話,可以不使用它。
  • LLDB: Android Studio 上面除錯原生代碼的工具。

你可以使用 SDK Manager 來安裝上述元件:

  1. 開啟一個專案,從選單欄中選擇 Tools > Android > SDK Manager
  2. 點選 SDK Tools
    選項卡。
  3. 勾選 LLDB,CMakeNDK。如圖一:

Figure-1.png-34.8kB

  1. 點選 Apply,然後點選 OK
  2. 當安裝完成後,點選 Finish,然後點選 OK

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

建立一個支援 native code 的專案和建立普通的 Android studio 工程很像。但是有幾點需要留意的地方:

  1. Configure your new project 選項中,勾選 Include C++ Support 選項。
  2. 點選 Next,後面的流程和建立普通的 Android studio 工程一樣。
  3. Customize C++ Support 選項卡中。你有下面幾種方式來自定義你的專案:

    • C++ Standard:點選下拉框,可以選擇標準 C++,或者選擇預設 CMake 設定的 Toolchain Default 選項。
    • Exceptions Support:如果你想使用有關 C++ 異常處理的支援,就勾選它。勾選之後,Android Studio 會在 module 層的 build.gradle 檔案中的 cppFlags 中新增 -fexcetions 標誌。
    • Runtime Type Information Support:如果你想支援 RTTI,那麼就勾選它。勾選之後,Android Studio 會在 module 層的 build.gradle 檔案中的 cppFlags 中新增 -frtti 標誌。
  4. 點選 “Finish”。

當 Android Studio 完成新專案建立後,開啟 Project 面板,選擇 Android 檢視。Android Studio 會新增 cppExternal Build Files 目錄。

Figure-2.png-41.4kB

  1. cpp 目錄存放你所有 native code 的地方,包括原始碼,標頭檔案,預編譯專案等。對於新專案,Android Studio 建立了一個 C++ 模板檔案:native-lib.cpp,並且將該檔案放到了你的 app 模組的 src/main/cpp/ 目錄下。這份模板程式碼提供了一個簡答的 C++ 函式:stringFromJNI(),該函式返回一個字串:”Hello from C++”。
  2. External Build Files 目錄是存放 CMake 或 ndk-build 構建指令碼的地方。有點類似於 build.gradle 檔案告訴 Gradle 如何編譯你的 APP 一樣,CMake 和 ndk-build 也需要一個指令碼來告知如何編譯你的 native library。對於一個新的專案,Android Studio 建立了一個 CMake 指令碼:CMakeLists.txt,並且將其放到了你的 module 的根目錄下。

編譯執行示例 APP

當你點選 Run 按鈕,Android Studio 會編譯並啟動一個 APP ,然後在 APP 中顯示一段文字”Hello from C++”。從編譯到執行示例 APP 的流程簡單歸納如下:

  1. Gradle 呼叫外部構建指令碼,也就是 CMakeLists.txt
  2. CMake 會根據構建指令碼的指令去編譯一個 C++ 原始檔,也就是 native-lib.cpp,並將編譯後的產物扔進共享物件庫中,並將其命名為 libnative-lib.so,然後 Gradle 將其打包到 APK 中。
  3. 在執行期間,APP 的 MainActivity 會呼叫 System.loadLibrary() 方法,載入 native library。而這個庫的原生函式,stringFromJNI(),就可以為 APP 所用了。
  4. MainActivity.onCreate() 方法會呼叫 stringFromJNI(),然後返回 “Hello from C++”,並更新 TextView 的顯示。

注意:Instant Run 並不相容使用了 native code 的專案。Android Studio 會自動禁止 Instant Run 功能。

如果你想驗證一下 Gradle 是否將 native library 打包進了 APK,你可以使用 APK Analyzer:

  1. 選擇 Build > Analyze APK
  2. app/build/outputs/apk/ 路徑中選擇 APK,並點選 OK
  3. 如下圖,在 APK Analyzer 視窗中,選擇 lib/<ABI>/,你就可以看見 libnative-lib.so

Figer-3.png-58.5kB

將 C/C++ 程式碼新增到現有的專案中

如果你想將 native code 新增到一個現有的專案中,請按照下面的步驟操作:

  1. 建立新的 native source 檔案,並將其新增到你的 Android Studio 專案中。如果你已經有了 native code,也可以跳過這一步。
  2. 建立一個 CMake 構建指令碼。如果你已經有了一個 CMakeLists.txt 構建指令碼,或者你想使用 ndk-build 然後有一個 Android.mk 構建指令碼,也可以跳過這一步。
  3. 將你的 native library 與 Gradle 關聯起來。Gradle 使用構建指令碼將原始碼匯入到你的 Android Studio 專案中,並且將你的 native library (也就是 .so 檔案)打包到 APK 中。

一旦你配置好了專案,你就可以在 Java 程式碼中,使用 JNI 框架開呼叫原生函式(native functions)。只需要點選 Run 按鈕,就可以編譯執行你的 APP 了。

建立新的 native source 檔案

請按照下面的方法來建立一個 cpp/ 目錄和原始檔(native source files):

  1. 開啟IDE左邊的 Project 面板,選擇 Project 檢視。
  2. 找到你專案的 module > src 目錄,右鍵點選 main 目錄,選擇 New > Directory
  3. 輸入目錄的名字(比如 cpp),然後點選 OK
  4. 右鍵點選剛才建立好的目錄,選擇 New > C/C++ Source File
  5. 輸入檔名,比如 native-lib
  6. Type 選單下拉選項中,選擇原始檔的擴充套件字尾名,比如 .cpp
  7. 如果你也想建立一個頭檔案,點選 Create an associated header 選項框。
  8. 點選 OK

建立 CMake 構建指令碼

如果沒有一個 CMake 構建指令碼,你需要自己手動建立一個,並新增一些合適的 CMake 命令。CMake 構建指令碼是一個空白的文字文件(字尾為 .txt 的檔案),名字必須為 CMakeLists.txt

注意:如果你的專案使用了 ndk-build,你就不需要建立 CMake 構建指令碼,只需要提供一個路徑鏈,將你的 Android.mk 檔案連結到 Gradle 中即可。

將一個空白的文字文件變成一個 CMake 構建指令碼,你需要這麼做:

  1. 開啟 IDE 左邊的 Project 面板,選擇 Project 檢視。
  2. 在你的 module 根目錄下,右鍵,選擇 New > File
  3. 輸入 “CMakeLists.txt” 作為檔名,並點選 OK

現在,你可以新增 CMake 命令來配置你的構建指令碼了。為了讓 CMake 將原始碼(native source code)編譯成 native library。需要在編譯檔案中新增 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(),將一個原始檔(source file)或庫新增到你的 CMake 構建指令碼,同步你的專案,然後你會發現 Android studio 將關聯的標頭檔案也顯示了處理。然而,為了讓 CMake 在編譯時期能定位到你的標頭檔案,你需要在 CMake 構建指令碼中新增 include_directories() 命令,並指定標頭檔案路徑:

add_library(...)

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

然後,按照約定,CMake 會將生成的 library 命名為下面的形式:

lib*library-name*.so

比如,如果你在構建指令碼中,將 library 命名為 “native-lib”,那麼 CMake 會建立叫 libnative-lib.so 的檔案。但是,當你將 library 載入到 Java 程式碼中的時候, 你需要使用在 CMake 中指定的名稱:

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

注意:如果你將 CMake 腳本里面的 library 重新命名了,或者移除了。你需要清理一下你的工程。在 IDE 的選單欄中選擇 Build > Clean Project

Android Studio 會在 Project 面板中的 cpp 目錄中自動新增原始檔和標頭檔案。你可以多次使用 add_library() 命令,來新增額外的 library。

新增 NDK APIs

Android NDK 提供了一些有用的 native APIs。將 NDK librarys 新增到 CMakeLists.txt 指令碼檔案中,就可以使用這些 API 了。

預編譯的 NDK librarys 已經存在在 Android 平臺中了,所以你不需要編譯它們,或者是將其打包到你的 APK 中。因為這些 NDK librarys 已經是 CMake 搜尋路徑的一部分,你甚至不需要提供你本地安裝的 NDK 路徑。你只需要向 CMake 提供你想使用的 library 名字。

find_library() 命令新增到你的 CMake 構建指令碼中,這樣就可以定位 NDK library 的位置,並將其位置儲存在一個變數之中。你可以在構建指令碼的其他地方使用這個變數,來代指 NDK library。下面的示例程式碼將 Android-specific log support library 的位置儲存到變數 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 )

NDK 同樣也包含一些只包含原始碼的 library,這些就需要你去編譯,然後連結到你的本地庫(native library)。你可以在 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} )

新增其他的預編譯庫

新增預編譯庫和新增本地庫(native library)類似。由於預編譯庫是已經構建好的,你想就要使用 IMPORTED 標誌去告訴 CMake ,你只需要將其匯入到你的專案中即可:

add_library( imported-lib
             SHARED
             IMPORTED )

然後你需要使用 set_target_properties() 命令去指定庫的路徑,就像下面的程式碼那樣。

一些庫會根據不同的 CPU 使用不同的包,或者是 Application Binary Interfaces(ABI),並且將他們歸類到不同的目錄中。這樣做的好處是,可以充分發揮特定的 CPU 架構。你可以使用 ANDROID_ABI 路徑變數,將多個 ABI 版本的庫新增到你的 CMake 構建指令碼中。這個變數使用了一些 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} )

當你構建你的 APP 的時候,Gradle 會自動將匯入的庫打包到你的 APK 中。你可以使用 APK Analyzer 來檢查。

關聯本地庫與 Gradle

為了將本地庫與 Gradle 相關聯,你需要在 CMake 或 ndk-build 構建指令碼中提供一個路徑地址。當你構建你的 APP 時,Gradle 會將 CMake 或 ndk-build 作為一個依賴執行,然後將共享庫(.so 檔案)打包到你的 APK 中。Gradle 同樣使用構建指令碼來識別哪些檔案需要匯入到 Android Studio 專案,你可以從 Project 視窗面板中看到相應的檔案。如果你還沒有一個為 native sources 準備的構建指令碼,你需要先建立一個。

使用 Android Studio 圖形化介面

你可以使用 Android Studio 的圖形化介面來將 Gradle 與外部 CMake 或者 ndk-build 專案關聯起來。

  1. 開啟 IDE 左邊的 Project 面板,選擇 Android 檢視。
  2. 右鍵點選你想連結本地庫的 module,比如 app module,然後從選單中選擇 Link C++ Project with Gradle。你應該能看見一個和下圖很像的對話方塊。
  3. 在下拉選單中,選擇 CMake 或者 ndk-build
    a. 如果你選擇 CMake,需要在 Project Path 中指定 CMakeLists.txt 指令碼檔案的路徑。
    b. 如果你選擇 ndk-build,你需要在 Project Path 中指定 Android.mk 指令碼檔案的路徑。

    Figure 3.jpg-86.4kB

  4. 點選 OK

手動配置 Gradle

如果要手動將 Gradle 與你的本地庫相關聯,你需要在 module 層級的 build.gradle 檔案中新增 externalNativeBuild {} 程式碼塊,並且在該程式碼塊中配置 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"
    }
  }
}

可選配置

你可以在你的 module 層級的 build.gradle 檔案中的 defaultConfig {} 程式碼塊中,新增 externalNativeBuild {} 程式碼塊,為 CMake 或 ndk-build 配置一些額外引數。當然,你也可以在你的構建配置中的其他每一個生產渠道重寫這些屬性。

比如,如果你的 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 "-DCMAKE_VERBOSE_MAKEFILE=TRUE"

        // 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"
        }
      }
    }
  }

  // You use this block to link Gradle to your CMake or ndk-build script.
  externalNativeBuild {
    cmake {...}
    // or ndkBuild {...}
  }
}

指定 ABI

一般情況下,Gradle 會將你的本地庫構建成 .so 檔案,然後將其打包到你的 APK 中。如果你想 Gradle 構建並打包某個特定的 ABI 。你可以在你的 module 層級的 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 構建、依賴你希望的東西,你就需要在 defaultConfig.externalNativeBuild.cmake {} 程式碼塊或 defaultConfig.externalNativeBuild.ndkBuild {} 程式碼塊中,配置其他的 abiFilters 標籤。Gradle 會構建這些 ABI 配置,但是隻會將 defaultConfig.ndk {} 程式碼塊中指定的東西打包到 APk 中。