1. 程式人生 > >Android NDK 開發(五)AndroidStudio 2.2 NDK的開發環境搭建

Android NDK 開發(五)AndroidStudio 2.2 NDK的開發環境搭建

前言

之前一直在用Eclipse 做開發,直到今年年初才將專案遷移到Google 推薦的AndroidStudio上面,畢竟這是一個趨勢,可誰知道事情根本沒有我想的那麼簡單,這期間遇到了N多坑,我想這些坑可能大家也有可能遇到,不在這裡詳細敘述。最終一個個問題的去解決,走完了這些坑,覺得還挺好用的,Eclipse 是一個吃記憶體的IDE,反正我每次開啟,編寫程式碼的時候就會卡,有時候還是死掉,已無力吐糟~~,AndroidStudio的介面更人性化,除此之外,還有更多的功能。所以決定把之前在正在eclipse上開發的一個使用NDK開發的android專案轉移到AndroidStudio上,在2.2之前對C/C++ 支援不是很友好,沒有語法提示,編譯也不方便等問題,所以期待了的Android Studio 2.2 版本穩定版終於在前段釋出,一直未來得及嘗試,今天把整個過程記下來,希望能給你有所幫助。

正文

1. 下載開發工具和NDK(windows10 64位環境)

Android Studio 2.2 的NDK開發支援 CMake和ndk-build兩種方式,預設的是CMake編譯的,編譯和除錯需要下載安裝以下元件:

  • CMake: Android Studio 預設使用 CMake 編譯原生庫,如果你只打算用ndk-build來編譯的話,你就不需要這個元件。

  • LLDB: 使用它來除錯原生代碼。

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

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

  1. 已安裝的軟體包如有更新,其旁邊的複選框中會顯示短劃線 這裡寫圖片描述

  2. 開啟一個專案,從選單欄中選擇 Tools > Android > SDK Manager

  3. 點選 SDK Tools 選項卡。

  4. 勾選 LLDBCMakeNDK 。如圖一:

這裡寫圖片描述

點選 Apply ,然後點選 OK 。

當安裝完成後,點選 Finish ,然後點選 OK 。

建立支援Native Code新專案

建立一個支援 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 會新增 cpp 和 External Build Files 資料夾。

這裡寫圖片描述

  1. cpp 資料夾存放你所有 native code 的地方,包括原始碼,標頭檔案,預編譯專案等。對於新專案,Android Studio 建立了一個 C++ 模板檔案: native-lib.cpp ,並且將該檔案放到了你的 app 模組的 src/main/cpp/ 目錄下。這份模板程式碼提供了一個簡答的 C++ 函式: stringFromJNI() ,該函式返回一個字串:”Hello from C++”

  2. External Build Files 資料夾是存放 CMakendk-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 。

這裡寫圖片描述

將 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() 命令:

# 設定建立你的本地庫所需的CMake的最低版本。
# 這為了確保某些功能在你構建的時候是可用的。

cmake_minimum_required(VERSION 3.4.1)

# 指定庫名
# 並提供原始碼的相對路徑. 
# 你可以通過add.library()命令定義多個庫,CMake會去構建他們,
# 當你構建App 的時候,Gradle 會自動把庫打包到你的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的使用命名庫的檔案的約定下面的形式:

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( #指定目標庫。
                       imported-lib

                       #指定要定義的引數。
                       PROPERTIES IMPORTED_LOCATION

                       #提供的路徑,你要匯入的庫。
                       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 來檢查。有關CMake的命令的詳細資訊,請參閱CMake的文件。

關聯本地庫與 Gradle

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

一旦你使用Gradle關聯了本地專案,AndroidStudio會在視窗更新專案的C/C++原始檔和本地庫,和你外部構建的指令碼檔案。

注意: 當你改變Gradle配置的時候,請確保在工具欄中點選Sync Project

使用 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 指令碼檔案的路徑。

  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來構建編譯不同的平臺,但是,如果你想控制 Gradle 構建什麼,獨立打包到你的apk中,那就在defaultConfig.externalNativeBuild.cmake {} 程式碼塊或 defaultConfig.externalNativeBuild.ndkBuild {} 程式碼塊中,配置其他的 abiFilters 標籤。Gradle 會構建那些 ABI 配置,但是隻會將 defaultConfig.ndk {} 程式碼塊中指定的配置打包到 apk 中。