Android Studio添加原生庫並自動構建


[時間:2017-09] [狀態:Open]
[關鍵詞:Android,Android Studio,gradle,native,c,c++,cmake,原生開發,ndk-build]

0 引言

最近在工作中遇到了升級Android Studio 2.3.3穩定版之后,無法編譯jar包的問題。之后尋找AS文檔-探索 Android Studio發現。可以通過AS創建和編譯jar包,順便看到支持原生開發,可以直接在AS中調試c/c++代碼,這是非常不錯的功能。終於可以擺脫純打日志的開發環境了。

本系列文章也就因此而出現。我希望閱讀文本文之后,大家能夠基本了解如何使用新版的Android Studio開發原生引用。
這里說明一點,本文是參考谷歌的Android Studio官網來的,也可以認為是一種翻譯版。可以訪問的話你可以直接查看對應內容。

這是第二篇:現有AS項目中添加原生庫。

1 環境准備

請參考本系列的第一篇:原生庫示例創建及驗證中的要求,按照和更新Android Studio。本文需要事先創建一個AS工程,可以不支持原生庫。
比如我使用默認的blank Activity創建了一個名為ExistingApplication的項目。

2 主要步驟

如果需要在現有項目中添加原生代碼,需要參考下面步驟:(下列步驟推薦用於添加全新的庫)

  1. 創建native源文件,並添加到工程中;
    如果你僅僅是導入預構建的原生庫或者源碼可以跳過此步驟。
  2. 創建一個CMake構建腳本,用於構建你的源代碼並創建庫。如果是導入或者鏈接預構建的庫也需要該腳本。
    如果你的原生庫已經使用CMakeLists.txt構建腳本,或者使用ndk-build構建並包含Android.mk構建腳本,可以跳過此步驟。
  3. 將Gradle與原生庫鏈接起來,只提供下CMake或ndk-build腳本文件所在路徑即可。
    Gradle會自動使用構建腳本導入源代碼,並將其以SO文件的形式打包到APK中。

通過上面三個步驟,你就可以在AS中使用JNI框架調試和驗證原生代碼了。

下面分開介紹下各個步驟。

步驟一:創建native源文件,並添加到工程中

在app模塊中創建cpp目錄用於保存新的原生代碼,步驟如下:

  1. 切換到Project Files視圖,並找到模塊>src,在main目錄上右鍵點擊,選擇新建>目錄。
  2. 輸入一個目錄名字(比如輸出cpp),並點擊確定。
  3. 右鍵點擊你剛創建的目錄,並選擇新建 > C/C++ 源文件。
  4. 輸入要創建的源文件的名字,比如native_lib。
  5. 從下拉列表框中,選擇源文件的擴展名,比如.cpp。
  6. 如果需要創建頭文件,勾選對應的復選框。點擊確定即可。

步驟二:創建CMake構建腳本

CMake構建腳本是一個名為CMakeLists.txt的純文本文件。本部分僅包含在CMake構建本地庫時常用的基礎命令,更詳細的介紹建議參考CMake官方文檔

請注意:如果使用ndk-build,無需提供CMake構建腳本。你可以通過提供你的Android.mk文件所在的目錄將其與Gradle鏈接起來。

以下步驟是如何創建CMake構建腳本:

  1. 打開IDE左側的工程面板,並選擇Project視圖。
  2. 右鍵點擊模塊的根目錄,並選擇新建 > 文件。
  3. 輸入CMakeLists.txt作為文件名,並點擊確定。

創建之后,你就可以在構建腳本中添加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( # 指定庫的名稱
             native-lib

             # 設置庫類型為共享庫
             SHARED

             # 提供源碼所在的路徑
             src/main/cpp/native-lib.cpp )

在構建腳本中使用add_library()添加源文件或庫時,AS會在Project視圖中自動顯示對應的頭文件。然而,為了讓CMake可以在編譯時定位頭文件的目錄,需要在CMake構建腳本中添加include_directories()命令,並指定頭文件所在目錄。

add_library(...)

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

CMake用於命名庫文件的格式如下:liblibrary-name.so
例如,如果你在構建腳本中指定共享庫的名字為"native-lib",CMkae將創建一個名為libnative-lib.so的文件。但在Java代碼載入庫時,需要使用你在CMake構建腳本中指定的名字:

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

添加NDK APIs

Android NDK中提供了一系列有用的原生API及庫。你可以在CMakeLists.txt腳本文件中使用這些API,只需要包含NDK庫即可。

預構建的NDK庫已經在安卓平台上存在,你不需要將其構建或打包到你的APK中。由於NDK庫是CMake搜索路徑的一部分,你也無須指定你的NDK安裝目錄,你只需要提供在你的原生庫中所依賴的ndk名稱即可。
在你的CMake構建腳本中添加find_library()命令,用於定位NDK庫所在位置,並將其存儲在變量中。你可以構建腳本的其他部分使用該變量來引用NDK庫。例如:下面示例代碼中定位了安卓相關的log支持庫,並將其路徑保存在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庫中的函數,你需要使用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()命令即可。其中你可以使用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(應用程序二進制接口)提供了不同的版本,並將其保存到單獨目錄下。這種方法可以幫助庫僅加載需要版本的庫。如果需要在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/ )

為了將預構建的庫連接到你自己的原生庫中,需要在CMake構建腳本中將其添加到target_link_libraries()命令中:

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

為了將預構建的庫打包到你的APK中,你需要手工配置Gradle,在sourceSets塊中包含你的.so文件所在路徑。在構建APK之后,你可以使用APK Analyzer驗證Gradle究竟打包了那個庫。

包含其他CMake工程

如果你想構建多個CMake工程並將其輸出導入到你的Android工程中,你可以將CMakeLists.txt作為頂級CMake構建腳本(同時在Gradle中指定該文件),然后將需添加的CMake工程作為該腳本的依賴項。下面的頂級CMake構建腳本使用add_subdirectory()命令來指定另一個CMakeLists.txt文件作為構建依賴項,然后將其輸出鏈接到當前工程中,這樣工作機制和預構建庫類似。

# Sets lib_src_DIR to the path of the target CMake project.
set( lib_src_DIR ../gmath )

# Sets lib_build_DIR to the path of the desired output directory.
set( lib_build_DIR ../gmath/outputs )
file(MAKE_DIRECTORY ${lib_build_DIR})

# Adds the CMakeLists.txt file located in the specified directory
# as a build dependency.
add_subdirectory( # Specifies the directory of the CMakeLists.txt file.
                  ${lib_src_DIR}

                  # Specifies the directory for the build outputs.
                  ${lib_build_DIR} )

# Adds the output of the additional CMake build as a prebuilt static
# library and names it lib_gmath.
add_library( lib_gmath STATIC IMPORTED )
set_target_properties( lib_gmath PROPERTIES IMPORTED_LOCATION
                       ${lib_build_DIR}/${ANDROID_ABI}/lib_gmath.a )
include_directories( ${lib_src_DIR}/include )

# Links the top-level CMake build output against lib_gmath.
target_link_libraries( native-lib ... lib_gmath )

步驟三:Gradle鏈接原生庫

為了在Gradle中了鏈接你的原生庫,你需要提供你的CMake或nkd-build腳本文件的路徑。配置好之后,在實際構建APP時,Gradle會將CMake/ndk-build作為依賴項,並將共享庫打包到APK中。Gradle也會參考該構建腳本,將源文件加載到Android Studio工程,以便你可以直接訪問。如果你的native源代碼沒有對應的CMake構建腳本,你需要參考上一個步驟創建一個。
Android工程總的每個module只能鏈接一個CMake/ndk-build腳本文件。因此,假如你想同時構建多個CMake工程並打包其輸出,你可用一個CMakeLists.txt作為頂層的CMake構建腳本(並在Gradle中指定該文件路徑),然后將其他CMake工程作為該構建腳本的依賴項。同樣的,如果你使用ndk-build,你可以在頂層的Android.mk腳本文件中包含其他Makefile。
當你在Gradle中配置好native項目之后,Android Studio將會更新Project面板來顯示你的源代碼文件和原生庫(cpp),在External Build Files中顯示外部構建腳本。

使用Android Studio UI配置

你可以使用Android Studio UI來實現將Gradle鏈接到CMake/ndk-build。

  1. 打開IDE左側的Project面板,並選擇Android視圖。
  2. 右鍵點擊需要鏈接原生庫的module,比如app module,然后從菜單中選在Link C++ Project with Gradle。你將看到類似圖1的對話框。
  3. 從下拉列表框中選擇CMake或ndk-build。
    1. 如果使用CMake,在Project Path右側可以選擇你需要添加的CMakeLists.txt腳本文件。
    2. 如果使用ndk-build, 使用Project Patch右側的選擇對話框選擇Android.mk腳本文件。如果Application.mk位於和Android.mk相同的目錄,也會被自動加載。

圖1 使用Android Studio對話框鏈接外部C++工程

  1. 點擊確定即可。完成配置。

手動配置Gradle

手動配置Gradle,需要在你的module層的build.gradle文件中添加externalNativeBuild塊,並將其使用cmakendkBuild塊配置。

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

  // If you want Gradle to package prebuilt native libraries
  // with your APK, modify the default source set configuration
  // to include the directory of your prebuilt .so files as follows.
  sourceSets {
      main {
          jniLibs.srcDirs 'imported-lib/src/', 'more-imported-libs/src/'
      }
  }
}

指定可選配置項

你可以為CMake或ndk-build指定可選參數和標志,通過配置你的module層的build.gradle文件中的defaultConfig塊下的externalNativeBuild子塊。和defaultConfig塊中其它屬性類似,你可以在你的產品級構建配置中覆蓋這些屬性。
例如,如果你的CMake或ndk-build工程定義了多個原生庫,你可以使用target屬性來為特定產品構建和打包一個這些庫的子集。下面代碼展示了如何通過配置該屬性實現上述需求:

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 the ndkBuild block.
      cmake {

        // Passes optional arguments to CMake.
        arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"

        // Sets optional flags for the C compiler.
        cFlags "-fexceptions", "-frtti"

        // 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 {...}
  }
}

了解更多相關信息,請參考Configure Build Variants。對於CMake支持的參數屬性,請參考CMake Variables

指定ABIs

默認情況下,Gradle會按照NDK支持的ABI將你的原生庫編譯為獨立的.so文件。並將它們全部打包到APK中。如果你希望Gradle僅構成和打包指定ABI版本的原生庫,你就需要在module層的build.gradle文件中指定ndk.abiFilters標志,如下所示:

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

    // Similar to other properties in the defaultConfig block,
    // you can configure the ndk block for each product flavor
    // in your build configuration.
    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 {...}
}

如果你希望減小APK的大小,請參考基於ABI的多APK打包配置

3 小結及后續

本文作為Android創建的第二篇,整體比較簡單,內容主要是翻譯部分,整理並介紹了源代碼和gradle、CMake構建腳本。接下來我們將關注如何使用AS構建JAR包。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM