AndroidStudio用Cmake方式編譯NDK程式碼(cmake配置.a庫)
AndroidStudio用Cmake方式編譯NDK程式碼(cmake配置.a庫)
1.cmake是什麼?
CMake是一個跨平臺的安裝(編譯)工具,可以用簡單的語句來描述所有平臺的安裝(編譯過程)。他能夠輸出各種各樣的makefile或者project檔案,能測試編譯器所支援的C++特性,類似UNIX下的automake。
谷歌從AndroidStudio2.2以上就添加了Cmake方式來編譯NDK程式碼,並從NDK例子看出,預設編譯的方式就是cmake方式。
2.谷歌官方的用cmake方式編譯NDK的教程
谷歌從AndroidStudio2.2以上就添加了Cmake方式來編譯NDK程式碼,並從NDK例子看出,預設編譯的方式就是cmake方式。
如果您希望向現有專案新增原生程式碼,請執行以下步驟:
- 建立新的原生原始檔並將其新增到您的 Android Studio 專案中。
- 如果您已經擁有原生程式碼或想要匯入預構建的原生庫,則可以跳過此步驟。
- 建立 CMake 構建指令碼,將您的原生原始碼構建到庫中。如果匯入和關聯預構建庫或平臺庫,您也需要此構建指令碼。
- 如果您的現有原生庫已經擁有
CMakeLists.txt
構建指令碼或者使用 ndk-build 幷包含Android.mk
構建指令碼,則可以跳過此步驟。
- 如果您的現有原生庫已經擁有
- 提供一個指向您的 CMake 或 ndk-build 指令碼檔案的路徑,
配置完專案後,您可以使用 JNI 框架從 Java 程式碼中訪問您的原生函式。要構建和執行應用,只需點選 Run 。Gradle 會以依賴項的形式新增您的外部原生構建流程,用於編譯、構建原生庫並將其隨 APK 一起封裝。
建立新的原生原始檔
要在應用模組的主原始碼集中建立一個包含新建原生原始檔的 cpp/
目錄,請按以下步驟操作:
- 從 IDE 的左側開啟 Project
- 導航到您的模組 > src,右鍵點選 main 目錄,然後選擇 New > Directory。
- 為目錄輸入一個名稱(例如
cpp
)並點選 OK。 - 右鍵點選您剛剛建立的目錄,然後選擇 New > C/C++ Source File。
- 為您的原始檔輸入一個名稱,例如
native-lib
。 - 從 Type 下拉選單中,為您的原始檔選擇副檔名,例如
.cpp
。- 點選 Edit File Types ,您可以向下拉選單中新增其他檔案型別,例如
.cxx
或.hxx
。在彈出的 C/C++ 對話方塊中,從 Source Extension 和 Header Extension 下拉選單中選擇另一個副檔名,然後點選 OK。
- 點選 Edit File Types ,您可以向下拉選單中新增其他檔案型別,例如
- 如果您還希望建立一個標標頭檔案,請選中 Create an associated header 複選框。
- 點選 OK。
建立 CMake 構建指令碼
如果您的原生原始檔還沒有 CMake 構建指令碼,則您需要自行建立一個幷包含適當的 CMake 命令。CMake 構建指令碼是一個純文字檔案,您必須將其命名為 CMakeLists.txt
。本部分介紹了您應包含到構建指令碼中的一些基本命令,用於在建立原生庫時指示 CMake 應使用哪些原始檔。
注:如果您的專案使用 ndk-build,則不需要建立 CMake 構建指令碼。提供一個指向您的 Android.mk
檔案的路徑,將 Gradle 關聯到您的原生庫。
要建立一個可以用作 CMake 構建指令碼的純文字檔案,請按以下步驟操作:
- 從 IDE 的左側開啟 Project 窗格並從下拉選單中選擇 Project 檢視。
- 右鍵點選您的模組的根目錄並選擇 New > File。
注:您可以在所需的任意位置建立構建指令碼。不過,在配置構建指令碼時,原生原始檔和庫的路徑將與構建指令碼的位置相關。
- 輸入“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 定義要從其他原始檔構建的更多庫。
新增 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} )
新增其他預構建庫
新增預構建庫與為 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 會自動將匯入的庫封裝到 APK 中。您可以使用 APK 分析器驗證 Gradle 將哪些庫封裝到您的 APK 中。如需瞭解有關 CMake 命令的詳細資訊,請參閱 CMake 文件。
匯入.a靜態庫
自 android studio 2.2 +後就集成了ndk開發, 自帶cmake 編譯器. 編寫ndk時候,配置很簡單。再也不需要用android.mk配置檔案。
新建一個帶ndk開發專案的結構是這樣的,
言歸正傳,那麼要新增第三方的xx.a連結庫呢?
通常我們把第三方提供的h資料夾,放在cpp的include裡面。這是規範,不是必須。而xxx.a庫放在src/main/jniLibs/armeabi目錄下。
本文章以新增libjsoncpp.a連線庫做例子
首先在cpp目錄下建立一個include資料夾,把jsoncpp官方提供的標頭檔案資料夾拷貝到include裡面(我這個專案有3個連結庫,jsoncpp, curl , openssl,另外2個僅做參考作用,與其無關)
第二步, 在app的src目錄的main下,建立一個資料夾,jniLibs,然後在jniLibs裡面再建立一個armeabi資料夾。
然後把libjsoncpp.a連結庫拷貝進去。
第三步, 動態庫與標頭檔案拷貝進去時候,是需要告訴編譯器做關聯的。在app目錄的src資料夾下有個CMakeLists.txt檔案,我們通過它編寫配置資訊。
將jsoncpp標頭檔案所在目錄告訴編譯,在裡面新增
- include_directories( src/main/cpp/include/jsoncpp)
如果有多個連結庫,那麼可以這樣
例如有三個連結庫,jsoncpp, currl , openssl.
- include_directories( src/main/cpp/include/jsoncpp
- src/main/cpp/include/curl
- src/main/cpp/include/openssl
- )
然後告訴編譯器,libjsoncppp.a在哪個目錄下,並指定連結庫的名稱
接著新增
- #新增json庫
- add_library(jsoncpp STATIC IMPORTED)
- set_target_properties(jsoncpp
- PROPERTIES IMPORTED_LOCATION
- ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libjsoncpp.a)
然後修改target_link_libraries,加多一行jsoncpp
- target_link_libraries(native-lib
- jsoncpp
- ${log-lib})
如果有多個,那麼可以多次新增。如
- #新增json庫
- add_library(jsoncpp STATIC IMPORTED)
- set_target_properties(jsoncpp
- PROPERTIES IMPORTED_LOCATION
- ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libjsoncpp.a)
- #新增curl網路請求
- add_library(curl STATIC IMPORTED)
- set_target_properties(curl
- PROPERTIES IMPORTED_LOCATION
- ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libcurl.a)
- #新增加密工具(md5, base64, des, aes , asa) part-1
- add_library(crypto STATIC IMPORTED)
- set_target_properties(crypto
- PROPERTIES IMPORTED_LOCATION
- ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libcrypto.a)
- #新增加密工具(md5, base64, des, aes , asa) des加密 part-2
- add_library(ssl STATIC IMPORTED)
- set_target_properties(ssl
- PROPERTIES IMPORTED_LOCATION
- ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libssl.a)
- target_link_libraries(native-lib
- jsoncpp
- curl
- crypto
- ssl
- ${log-lib})
然後gradle編譯就可以使用了。是不是很簡單?
將 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 與您的更改同步。
使用 Android Studio UI
您可以使用 Android Studio UI 將 Gradle 關聯到外部 CMake 或 ndk-build 專案:
- 從 IDE 左側開啟 Project 窗格並選擇 Android 檢視。
- 右鍵點選您想要關聯到原生庫的模組(例如 app 模組),並從選單中選擇 Link C++ Project with Gradle。您應看到一個如圖 4 所示的對話方塊。
- 從下拉選單中,選擇 CMake 或 ndk-build。
- 如果您選擇 CMake,請使用 Project Path 旁的欄位為您的外部 CMake 專案指定
CMakeLists.txt
指令碼檔案。 - 如果您選擇 ndk-build,請使用 Project Path 旁的欄位為您的外部 ndk-build 專案指定
Android.mk
指令碼檔案。如果Application.mk
檔案與您的Android.mk
檔案位於相同目錄下,Android Studio 也會包含此檔案。
圖 4.使用 Android Studio 對話方塊關聯外部 C++ 專案。
- 如果您選擇 CMake,請使用 Project Path 旁的欄位為您的外部 CMake 專案指定
- 點選 OK。
手動配置 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 也會包含此檔案。
指定可選配置
您可以在模組級 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 {...} } }
要詳細瞭解配置產品風味和構建變體,請參閱配置構建變體。如需瞭解您可以使用 arguments
屬性為 CMake 配置的變數列表,請參閱使用 CMake 變數。
指定 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 列表。
谷歌從AndroidStudio2.2以上就添加了Cmake方式來編譯NDK程式碼,並從NDK例子看出,預設編譯的方