[時間: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 主要步驟
如果需要在現有項目中添加原生代碼,需要參考下面步驟:(下列步驟推薦用於添加全新的庫)
- 創建native源文件,並添加到工程中;
如果你僅僅是導入預構建的原生庫或者源碼可以跳過此步驟。 - 創建一個CMake構建腳本,用於構建你的源代碼並創建庫。如果是導入或者鏈接預構建的庫也需要該腳本。
如果你的原生庫已經使用CMakeLists.txt構建腳本,或者使用ndk-build構建並包含Android.mk構建腳本,可以跳過此步驟。 - 將Gradle與原生庫鏈接起來,只提供下CMake或ndk-build腳本文件所在路徑即可。
Gradle會自動使用構建腳本導入源代碼,並將其以SO文件的形式打包到APK中。
通過上面三個步驟,你就可以在AS中使用JNI框架調試和驗證原生代碼了。
下面分開介紹下各個步驟。
步驟一:創建native源文件,並添加到工程中
在app模塊中創建cpp目錄用於保存新的原生代碼,步驟如下:
- 切換到Project Files視圖,並找到模塊>src,在main目錄上右鍵點擊,選擇新建>目錄。
- 輸入一個目錄名字(比如輸出cpp),並點擊確定。
- 右鍵點擊你剛創建的目錄,並選擇新建 > C/C++ 源文件。
- 輸入要創建的源文件的名字,比如native_lib。
- 從下拉列表框中,選擇源文件的擴展名,比如.cpp。
- 如果需要創建頭文件,勾選對應的復選框。點擊確定即可。
步驟二:創建CMake構建腳本
CMake構建腳本是一個名為CMakeLists.txt的純文本文件。本部分僅包含在CMake構建本地庫時常用的基礎命令,更詳細的介紹建議參考CMake官方文檔。
請注意:如果使用ndk-build,無需提供CMake構建腳本。你可以通過提供你的Android.mk文件所在的目錄將其與Gradle鏈接起來。
以下步驟是如何創建CMake構建腳本:
- 打開IDE左側的工程面板,並選擇Project視圖。
- 右鍵點擊模塊的根目錄,並選擇新建 > 文件。
- 輸入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。
- 打開IDE左側的Project面板,並選擇Android視圖。
- 右鍵點擊需要鏈接原生庫的module,比如app module,然后從菜單中選在Link C++ Project with Gradle。你將看到類似圖1的對話框。
- 從下拉列表框中選擇CMake或ndk-build。
- 如果使用CMake,在Project Path右側可以選擇你需要添加的CMakeLists.txt腳本文件。
- 如果使用ndk-build, 使用Project Patch右側的選擇對話框選擇Android.mk腳本文件。如果Application.mk位於和Android.mk相同的目錄,也會被自動加載。
- 點擊確定即可。完成配置。
手動配置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"
}
}
// 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包。