1. 程式人生 > >Android原生庫構建的三種姿態

Android原生庫構建的三種姿態

前言

自Android Studio 2.2釋出之後,AS開始支援CMake構建工具編譯構建原生程式碼庫,谷歌推薦使用CMake來構建新建的原生庫,不過為了兼顧老專案,AS還是支援ndk-build構建,除此之外,谷歌還推出了實驗性Gradle外掛來構建原生庫,至此AS工具支援3種方式來構建原生庫。
實驗性Gradle外掛由於依賴開發中的Gradle API,所以它是不穩定的,不過谷歌稱Android Studio團隊會繼續支援實驗性Gradle外掛,谷歌希望將來該外掛能取代當前的Gradle外掛,因為其緊密整合的特性對於C/C++開發者更為便利,例如,更靈活的依賴管理。因此,對於想在IDE與構建系統之間建立最靈活的介面的開發者,可以嘗試使用實驗性Gradle外掛。

這裡主要介紹下谷歌推薦的方式—Cmake。

PS:以下內容主要來自谷歌官方文件。

1、CMake

1.1、構建工具準備

  • 下載NDK:Android C/C++程式碼工具集
  • 下載Cmake:外部構建工具
  • 下載LLDB:Android Studio 上面除錯原生代碼的工具

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

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

1.2 建立 CMake 構建指令碼

建立一個CMakeList.txt構建指令碼檔案,然後用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/)

1.3、新增 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} )

1.4、新增其他的預編譯庫

新增預編譯庫和新增本地庫(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() 命令,並且將你的標頭檔案地址傳進去:

nclude_directories( imported-lib/include/ )

在 CMake 構建指令碼中使用 target_link_libraries() 命令,將預構建庫與你本地庫相關聯:

target_link_libraries( native-lib imported-lib app-glue ${log-lib} )

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

1.5、關聯本地庫與 Gradle

為了將本地庫與 Gradle 相關聯,你需要在 CMake 或 ndk-build 構建指令碼中提供一個路徑地址。當你構建你的 APP 時,Gradle 會將 CMake 或 ndk-build 作為一個依賴執行,然後將共享庫(.so 檔案)打包到你的 APK 中。

1.5.1、使用 Android Studio 圖形化介面

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

1.6、手動配置 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"
    }
  }
}

1.6.1、可選配置

你可以在你的 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 {...}
  }
}

1.6.2、指定 ABI

如果你想 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中。

官方文件:
https://developer.android.com/studio/projects/add-native-code.html#create-cmake-script

推薦一篇不錯的相關文章:
http://blog.csdn.net/mabeijianxi/article/details/68525164

2、ndk-build

ndk-build這裡不做詳細介紹,請自行檢視官方文件:
https://developer.android.com/ndk/guides/ndk-build.html

3、experimental-gradle-plugin

experimental-gradle-plugin這裡不做詳細介紹,請自行檢視官方文件:
http://tools.android.com/tech-docs/new-build-system/gradle-experimental
官方網址可能打不開,可檢視轉載網址:
http://www.cnblogs.com/tanlon/p/4731283.html