文章摘要
1、 c、c++項目的創建
2、添加C、C++項目依賴庫
4、配置Gradle,編譯lib so庫
搭配使用 Android Studio 2.2 或更高版本與 Android Plugin for Gradle 版本 2.2.0 或更高版本時,您可以將 C 和 C++ 代碼編譯到 Gradle 與 APK 一起打包的原生庫中,將這類代碼添加到您的應用中。您的 Java 代碼隨后可以通過 Java 原生接口 (JNI) 調用您的原生庫中的函數。
Android Studio 用于構建原生庫的默認工具是 CMake。由于很多現有項目都使用構建工具包編譯其原生代碼,Android Studio 還支持 ndk-build。
本頁面介紹的信息可以幫助您使用所需構建工具設置 Android Studio、創建或配置項目以支持 Android 上的原生代碼,以及構建和運行應用。
注:如果您的現有項目使用已棄用的 ndkCompile 工具,則應先打開 build.properties 文件,并移除以下代碼行,然后再將 Gradle 關聯到您的原生庫:
// Remove this line
android.useDeprecatedNdk = true
一、下載 NDK 和構建工具
要為您的應用編譯和調試原生代碼,您需要以下組件:
- Android 原生開發工具包 (NDK):這套工具集允許您為 Android 使用 C 和 C++ 代碼,并提供眾多平臺庫,讓您可以管理原生 Activity 和訪問物理設備組件,例如傳感器和觸摸輸入。
- CMake:一款外部構建工具,可與 Gradle 搭配使用來構建原生庫。如果您只計劃使用 ndk-build,則不需要此組件。
- LLDB:一種調試程序,Android Studio 使用它來調試原生代碼。
您可以使用 SDK 管理器安裝這些組件:
1、在打開的項目中,從菜單欄選擇 Tools > Android > SDK Manager。
2、點擊 SDK Tools 標簽。
3、選中 LLDB、CMake 和 NDK 旁的復選框,如圖 1 所示。
- 圖 1. 從 SDK 管理器中安裝 LLDB、CMake 和 NDK。
4、點擊 Apply,然后在彈出式對話框中點擊 OK。
5、安裝完成后,點擊 Finish,然后點擊 OK。
二、創建支持 C/C++ 的新項目
創建支持原生代碼的項目與創建任何其他 Android Studio 項目類似,不過前者還需要額外幾個步驟:
1、在向導的 Configure your new project 部分,選中 Include C++ Support 復選框。
2、點擊 Next。
3、正常填寫所有其他字段并完成向導接下來的幾個部分。
-
4、在向導的 Customize C++ Support 部分,您可以使用下列選項自定義項目:
- C++ Standard:使用下拉列表選擇您希望使用哪種 C++ 標準。選擇 Toolchain Default 會使用默認的 CMake 設置。
-
Exceptions Support:如果您希望啟用對 C++ 異常處理的支持,請選中此復選框。如果啟用此復選框,Android Studio 會將 --fexceptions
標志添加到模塊級 build.gradle 文件的 cppFlags中,Gradle 會將其傳遞到 CMake。 - Runtime Type Information Support:如果您希望支持 RTTI,請選中此復選框。如果啟用此復選框,Android Studio 會將 -frtti標志添加到模塊級 build.gradle 文件的 cppFlags 中,Gradle 會將其傳遞到 CMake。
5、點擊 Finish。
在 Android Studio 完成新項目的創建后,請從 IDE 左側打開 Project 窗格并選擇 Android 視圖。如圖 2 中所示,Android Studio 將添加 cpp 和 External Build Files 組:
- 圖 2. 您的原生源文件和外部構建腳本的 Android 視圖組。
注:此視圖無法反映磁盤上的實際文件層次結構,而是將相似文件分到一組中,簡化項目導航。
三、構建和運行示例應用
點擊 Run后,Android Studio 將在您的 Android 設備或者模擬器上構建并啟動一個顯示文字“Hello from C++”的應用。下面的概覽介紹了構建和運行示例應用時會發生的事件:
- 1、Gradle 調用您的外部構建腳本 CMakeLists.txt。
- 2、CMake 按照構建腳本中的命令將 C++ 源文件 native-lib.cpp 編譯到共享的對象庫中,并命名為 libnative-lib.so,Gradle 隨后會將其打包到 APK 中。
- 3、運行時,應用的 MainActivity 會使用 System.loadLibrary() 加載原生庫?,F在,應用可以使用庫的原生函數 stringFromJNI()。
- 4、MainActivity.onCreate() 調用 stringFromJNI(),這將返回“Hello from C++”并使用這些文字更新 TextView。
注:Instant Run 與使用原生代碼的項目不兼容。Android Studio 會自動停用此功能。
如果您想要驗證 Gradle 是否已將原生庫打包到 APK 中,可以使用 APK 分析器:
1、選擇 Build > Analyze APK。
2、從 app/build/outputs/apk/ 目錄中選擇 APK 并點擊 OK。
3、如圖 3 中所示,您會在 APK 分析器窗口的 lib/<ABI>/ 下看到 libnative-lib.so。
- 圖 3. 使用 APK 分析器定位原生庫。
提示:如果您想要試驗使用原生代碼的其他 Android 應用,請點擊 File > New > Import Sample 并從 Ndk 列表中選擇示例項目。
四、向現有項目添加 C/C++ 代碼
如果您希望向現有項目添加原生代碼,請執行以下步驟:
1、[創建新的原生源文件]并將其添加到您的 Android Studio 項目中。如果您已經擁有原生代碼或想要導入預構建的原生庫,則可以跳過此步驟。
2、[創建 CMake 構建腳本],將您的原生源代碼構建到庫中。如果導入和關聯預構建庫或平臺庫,您也需要此構建腳本。如果您的現有原生庫已經擁有 CMakeLists.txt 構建腳本或者使用 ndk-build 并包含 Android.mk 構建腳本,則可以跳過此步驟。
3、提供一個指向您的 CMake 或 ndk-build 腳本文件的路徑,將 Gradle 關聯到您的原生庫。Gradle 使用構建腳本將源代碼導入您的 Android Studio 項目并將原生庫(SO 文件)打包到 APK 中。
配置完項目后,您可以使用 JNI 框架從 Java 代碼中訪問您的原生函數。要構建和運行應用,只需點擊 Run 。Gradle 會以依賴項的形式添加您的外部原生構建流程,用于編譯、構建原生庫并將其隨 APK 一起打包。
4.1、創建新的原生源文件
要在應用模塊的主源代碼集中創建一個包含新建原生源文件的 cpp/ 目錄,請按以下步驟操作:
1、從 IDE 的左側打開 Project 窗格并從下拉菜單中選擇 Project 視圖。
2、導航到 您的模塊 > src,右鍵點擊 main 目錄,然后選擇 New > Directory。
3、為目錄輸入一個名稱(例如 cpp)并點擊 OK。
4、右鍵點擊您剛剛創建的目錄,然后選擇 New > C/C++ Source File。
5、為您的源文件輸入一個名稱,例如 native-lib。
-
6、從 Type 下拉菜單中,為您的源文件選擇文件擴展名,例如 .cpp。
- 點擊 Edit File Types,您可以向下拉菜單中添加其他文件類型,例如 .cxx 或 .hxx。在彈出的 C/C++ 對話框中,從 Source Extension 和 Header Extension 下拉菜單中選擇另一個文件擴展名,然后點擊 OK。
7、如果您還希望創建一個標頭文件,請選中 Create an associated header 復選框。
8、點擊 OK。
4.2、創建 CMake 構建腳本
如果您的原生源文件還沒有 CMake 構建腳本,則您需要自行創建一個并包含適當的 CMake 命令。CMake 構建腳本是一個純文本文件,您必須將其命名為 CMakeLists.txt。本部分介紹了您應包含到構建腳本中的一些基本命令,用于在創建原生庫時指示 CMake 應使用哪些源文件。
注:如果您的項目使用 ndk-build,則不需要創建 CMake 構建腳本。提供一個指向您的 Android.mk
文件的路徑,[將 Gradle 關聯到您的原生庫](后邊會介紹關聯操作)。
要創建一個可以用作 CMake 構建腳本的純文本文件,請按以下步驟操作:
- 1、從 IDE 的左側打開 Project 窗格并從下拉菜單中選擇 Project 視圖。
- 2、右鍵點擊 您的模塊 的根目錄并選擇 New > File。
注:您可以在所需的任意位置創建構建腳本。不過,在配置構建腳本時,原生源文件和庫的路徑將與構建腳本的位置相關。
- 3、輸入“CMakeLists.txt”作為文件名并點擊 OK。
現在,您可以添加 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( # 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() 向您的 CMake 構建腳本添加源文件或庫時,Android Studio 還會在您同步項目后在 Project 視圖下顯示關聯的標頭文件。不過,為了確保 CMake 可以在編譯時定位您的標頭文件,您需要將 include_directories() 命令添加到 CMake 構建腳本中并指定標頭的路徑:
add_library(...)
# Specifies a path to native header files.
include_directories(src/main/cpp/include/)
CMake 使用以下規范來為庫文件命名:lib庫名稱.so
例如,如果您在構建腳本中指定“native-lib”作為共享庫的名稱,CMake 將創建一個名稱為 libnative-lib.so 的文件。不過,在 Java 代碼中加載此庫時,請使用您在 CMake 構建腳本中指定的名稱:
static {
System.loadLibrary(“native-lib”);
}
注:如果您在 CMake 構建腳本中重命名或移除某個庫,您需要先清理項目,Gradle 隨后才會應用更改或者從 APK 中移除舊版本的庫。要清理項目,請從菜單欄中選擇 Build > Clean Project。
Android Studio 會自動將源文件和標頭添加到 Project 窗格的 cpp 組中。使用多個 add_library() 命令,您可以為 CMake 定義要從其他源文件構建的更多庫。
4.3、添加 NDK API
Android NDK 提供了一套實用的原生 API 和庫。通過將 NDK 庫包含到項目的 CMakeLists.txt 腳本文件中,您可以使用這些 API 中的任意一種。
預構建的 NDK 庫已經存在于 Android 平臺上,因此,您無需再構建或將其打包到 APK 中。由于 NDK 庫已經是 CMake 搜索路徑的一部分,您甚至不需要在您的本地 NDK 安裝中指定庫的位置 - 只需要向 CMake 提供您希望使用的庫的名稱,并將其關聯到您自己的原生庫。
將 find_library()命令添加到您的 CMake 構建腳本中以定位 NDK 庫,并將其路徑存儲為一個變量。您可以使用此變量在構建腳本的其他部分引用 NDK 庫。以下示例可以定位 Android 特定的日志支持庫并將其路徑存儲在 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 庫中調用函數,您需要使用 CMake 構建腳本中的 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() 命令,將源代碼編譯到原生庫中。要提供本地 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} )
4.4、添加其他預構建庫
添加預構建庫與為 CMake 指定要構建的另一個原生庫類似。不過,由于庫已經預先構建,您需要使用 IMPORTED標志告知 CMake 您只希望將庫導入到項目中:
add_library( imported-lib
SHARED
IMPORTED )
然后,您需要使用 set_target_properties() 命令指定庫的路徑,如下所示。
某些庫為特定的 CPU 架構(或應用二進制接口 (ABI))提供了單獨的軟件包,并將其組織到單獨的目錄中。此方法既有助于庫充分利用特定的 CPU 架構,又能讓您僅使用所需的庫版本。要向 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/ )
注:如果您希望打包一個并不是構建時依賴項的預構建庫(例如在添加屬于 imported-lib 依賴項的預構建庫時),則不需要執行以下說明來關聯庫。
要將預構建庫關聯到您自己的原生庫,請將其添加到 CMake 構建腳本的 target_link_libraries() 命令中:
target_link_libraries( native-lib imported-lib app-glue ${log-lib} )
五、將 Gradle 關聯到您的原生庫
要將 Gradle 關聯到您的原生庫,您需要提供一個指向 CMake 或 ndk-build 腳本文件的路徑。在您構建應用時,Gradle 會以依賴項的形式運行 CMake 或 ndk-build,并將共享的庫打包到您的 APK 中。Gradle 還使用構建腳本來了解要將哪些文件添加到您的 Android Studio 項目中,以便您可以從 Project 窗口訪問這些文件。如果您的原生源文件沒有構建腳本,則需要先創建 CMake 構建腳本,然后再繼續。
將 Gradle 關聯到原生項目后,Android Studio 會更新 Project 窗格以在 cpp 組中顯示您的源文件和原生庫,在 External Build Files 組中顯示您的外部構建腳本。
注:更改 Gradle 配置時,請確保通過點擊工具欄中的 Sync Project應用更改。此外,如果在將 CMake 或 ndk-build 腳本文件關聯到 Gradle 后再對其進行更改,您應當從菜單欄中選擇 Build > Refresh Linked C++ Projects,將 Android Studio 與您的更改同步。
5.1、使用 Android Studio UI
您可以使用 Android Studio UI 將 Gradle 關聯到外部 CMake 或 ndk-build 項目:
1、從 IDE 左側打開 Project 窗格并選擇 Android 視圖。
2、右鍵點擊您想要關聯到原生庫的模塊(例如 app 模塊),并從菜單中選擇 Link C++ Project with Gradle。您應看到一個如圖 4 所示的對話框。
-
3、從下拉菜單中,選擇 CMake 或 ndk-build。
- 1、如果您選擇 CMake,請使用 Project Path 旁的字段為您的外部 CMake 項目指定 CMakeLists.txt 腳本文件。
- 2、如果您選擇 ndk-build,請使用 Project Path 旁的字段為您的外部 ndk-build 項目指定 Android.mk 腳本文件。如果 Application.mk 文件與您的 Android.mk 文件位于相同目錄下,Android Studio 也會包含此文件。
- 圖 4. 使用 Android Studio 對話框關聯外部 C++ 項目。
4、點擊 OK。
5.2、手動配置 Gradle
要手動配置 Gradle 以關聯到您的原生庫,您需要將 externalNativeBuild {} 塊添加到模塊級 build.gradle 文件中,并使用 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"
}
}
}
注:如果您想要將 Gradle 關聯到現有 ndk-build 項目,請使用 ndkBuild {} 塊而不是 cmake {},并提供 Android.mk 文件的相對路徑。如果 Application.mk 文件與您的 Android.mk 文件位于相同目錄下,Gradle 也會包含此文件。
5.2.1、指定可選配置
您可以在模塊級 build.gradle 文件的 defaultConfig {} 塊中配置另一個 externalNativeBuild {} 塊,為 CMake 或 ndk-build 指定可選參數和標志。與 defaultConfig {} 塊中的其他屬性類似,您也可以在構建配置中為每個產品風味重寫這些屬性。
例如,如果您的 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 "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
// 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"
}
}
}
}
// Use this block to link Gradle to your CMake or ndk-build script.
externalNativeBuild {
cmake {...}
// or ndkBuild {...}
}
}
5.2.2、指定 ABI
默認情況下,Gradle 會針對 NDK 支持的 ABI 將您的原生庫構建到單獨的 .so
文件中,并將其全部打包到您的 APK 中。如果您希望 Gradle 僅構建和打包原生庫的特定 ABI 配置,您可以在模塊級 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 的大小,請考慮配置 ABI APK 拆分,而不是創建一個包含原生庫所有版本的大型 APK,Gradle 會為您想要支持的每個 ABI 創建單獨的 APK,并且僅打包每個 ABI 需要的文件。如果您配置 ABI 拆分,但沒有像上面的代碼示例一樣指定 abiFilters 標志,Gradle 會構建原生庫的所有受支持 ABI 版本,不過僅會打包您在 ABI 拆分配置中指定的版本。為了避免構建您不想要的原生庫版本,請為 abiFilters 標志和 ABI 拆分配置提供相同的 ABI 列表。