目錄
一、基礎概念
├──1.一、JNI
├──1.二、NDK
├──1.三、CMake與ndk-build
二、環境搭建
三、Native C++ 項目(HelloWord案例)
├── 3.一、項目建立(java、kotlin)
├── 3.二、CMake的應用(詳細講解)
├── 3.三、ndk-build的應用(詳細講解)java
JNI(Java Native Interface)Java本地接口,使得Java與C/C++具備交互能力android
NDK(Native Development Kit) 本地開發工具包,容許使用原生語言(C和C++)來實現應用程序的部分功能macos
Android NDK開發的主要做用:bash
一、特定場景下,提高應用性能; 二、代碼保護,增長反編譯難度; 三、生成庫文件,庫可重複使用,也便於平臺、項目間移植;
當咱們基於NDK開發出native功能後,一般須要編譯成庫文件,給Android項目使用。
目前,有兩種主流的編譯方式:__CMake__與ndk-buildapp
__CMake__與__ndk-build__是兩種不一樣的編譯工具(與Android代碼和C/C++代碼無關)工具
CMake性能
CMake是Androidstudio2.2以後引入的跨平臺編譯工具(特色:簡單易用,2.2以後是默認的NDK編譯工具) 如何配置: 一、建立CMakeLists.txt文件,配置CMake必要參數; 二、使用gradle配置CMakeLists.txt以及native相關參數; 如何編譯庫文件: 一、Android Studio執行Build便可;
ndk-build學習
ndk-build是NDK中包含的腳本工具(可在NDK目錄下找到該工具,爲了方便使用,一般配置NDK的環境變量) 如何配置: 一、建立Android.mk文件,配置ndk-build必要參數; 二、可選建立application.mk文件,配置ndk-build參數 (該文件的配置項可以使用gradle的配置替代); 三、使用gradle配置Android.mk以及native相關參數; 二、如何編譯庫文件(兩種方式): 一、Android Studio執行Build便可(執行了:Android.mk + gradle配置); 二、也可在Terminal、Mac終端、cmd終端中經過ndk-build命令直接構建庫文件(執行了:Android.mk)
JNI安裝
JNI 是JDK裏的內容,電腦上正確安裝並配置JDK便可 (JDK1.1以後就正式支持了);開發工具
NDK安裝
可從官網自行下載、解壓到本地,也可基於AndroidStudio下載解壓到默認目錄;gradle
編譯工具安裝
cmake 可基於AndroidStudio下載安裝;
ndk-build 是NDK裏的腳本工具,NDK安裝好便可使用ndk-build;
當前演示,使用的Android Studio版本以下(當前最新版):
啓動Android Studio --> 打開SDK Manager --> SDK Tools,以下圖所示:
咱們選擇NDK、CMake、LLDB(調試Native時纔會使用),選擇Apply進行安裝,等安裝成功後,NDK開發所依賴的環境也就都齊全了。
新建項目,選擇 Native C++,以下圖:
新建立的項目,默認已包含完整的native 示例代碼、cmake配置 ,以下圖:
這樣,咱們就能夠本身定義Java native方法,並在cpp目錄中寫native實現了,很方便。
可是,當咱們寫完native的實現代碼,但願運行APP,查看JNI的交互效果,此時,就須要使用編譯工具了,我們仍是先看一下Android Studio默認的Native編譯方式吧:CMake
在CMake編譯以前,我們應該先作哪些準備工做?
一、NDK環境是否配置正確? -- 若是未配置正確是沒法進行C/C++開發的,更不用說CMake編譯了 二、C/C++功能是否實現? -- 這次演示主要使用系統默認建立的native-lib.cpp文件,關於具體如何實現:後續文章再詳細講解 三、CMakeLists.txt是否建立並正確配置? -- 該文件是CMake工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果 四、gradle是否正確配置? -- gradle配置也是CMake工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果
除此以外,我們還應該學習CMake的哪些重要知識?
一、CMake工具編譯生成的庫文件默認在什麼位置?apk中庫文件又是在什麼位置? 二、CMake工具如何指定編譯生成的庫文件位置? 三、CMake工具如何指定生成不一樣CPU平臺對應的庫文件?
帶着這些問題,我們開始CMake之旅吧:
編譯前,建議先檢查下工程的NDK配置狀況(否則容易報一些亂七八糟的錯誤):
File --> Project Structure --> SDK Location,以下圖(我本地的Android Studio默認沒有給配置NDK路徑,那麼,須要本身手動指定一下):
由於本節主講CMake編譯工具,代碼就不單獨寫了,我們直接使用工程默認生成的native-liv.cpp,簡單調整一下native實現方法的代碼吧(修改返回文本信息):
因Native C++工程默認已配置好了CMakeLists.txt和gradle,因此我們可直接運行工程看效果,以下圖:
JNI交互效果咱們已經看到了,說明CMake編譯成功了。那麼,這到底是怎麼作到的呢?我們接着分析一下吧:
安卓工程編譯時,會執行CMake編譯,在 工程/app/build/.../cmake/ 中會產生對應的so文件,以下圖:
繼續編譯安卓工程,會根據build中的內容,生成咱們的*.apk安裝包文件。咱們找到、反編譯apk安裝包文件,查找so庫文件。原來在apk安裝包中,so庫都被存放在lib目錄中,以下圖:
在前面介紹CMake定義時,提到了CMake是基於CMakeLists.txt文件和gradle配置實現編譯Native類的。那麼,我們先來看一下CMakeLists.txt文件吧:
#cmake最低版本要求 cmake_minimum_required(VERSION 3.4.1) #添加庫 add_library( # 庫名 native-lib # 類型: # SHARED 是指動態庫,對應的是.so文件 # STATIC 是指靜態庫,對應的是.a文件 # 其餘類型:略 SHARED # native類路徑 native-lib.cpp) # 查找依賴庫 find_library( # 依賴庫別名 log-lib # 但願加到本地的NDK庫名稱,log指NDK的日誌庫 log) # 連接庫,創建關係( 此處就是指把log-lib 連接給 native-lib使用 ) target_link_libraries( # 目標庫名稱(native-lib 是我們要生成的so庫) native-lib # 要連接的庫(log-lib 是上面查找的log庫) ${log-lib})
實際上,CMakeList.txt可配置的內容遠不止這些,如:so輸出目錄,生成規則等等,有須要的同窗可查下官網。
接着,我們再看一下app的gradle又是如何配置CMake的呢?
apply plugin: 'com.android.application' android { compileSdkVersion 29 buildToolsVersion "29.0.1" defaultConfig { applicationId "com.qxc.testnativec" minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" //定義cmake默認配置屬性 externalNativeBuild { cmake { cppFlags "" } } } //定義cmake對應的CMakeList.txt路徑(重要) externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' }
實際上,gradle可配置的cmake內容也遠不止這些,如:abi、cppFlags、arguments等,有須要的同窗可查下官網。
若是但願將so庫生成到特定目錄,並讓項目直接使用該目錄下的so,應該如何配置呢?
比較簡單:須要在CMakeList.txt中配置庫的輸出路徑信息,即:
CMAKE_LIBRARY_OUTPUT_DIRECTORY
# cmake最低版本要求 cmake_minimum_required(VERSION 3.4.1) # 配置庫生成路徑 # CMAKE_CURRENT_SOURCE_DIR是指 cmake庫的源路徑,一般是build/.../cmake/ # /../jniLibs/是指與CMakeList.txt所在目錄的同級目錄:jniLibs (若是沒有會新建) # ANDROID_ABI 生成庫文件時,採用gradle配置的ABI策略(即:生成哪些平臺對應的庫文件) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}) # 添加庫 add_library( # 庫名 native-lib # 類型: # SHARED 是指動態庫,對應的是.so文件 # STATIC 是指靜態庫,對應的是.a文件 # 其餘類型:略 SHARED # native類路徑 native-lib.cpp) # 查找依賴庫 find_library( # 依賴庫別名 log-lib # 但願加到本地的NDK庫名稱,log指NDK的日誌庫 log) # 連接庫,創建關係( 此處就是指把log-lib 連接給native-lib使用 ) target_link_libraries( # 目標庫名稱(native-lib就是我們要生成的so庫) native-lib # 要連接的庫(上面查找的log庫) ${log-lib})
還須要在gradle中配置 jniLibs.srcDirs 屬性(即:指定了lib庫目錄):
sourceSets { main { jniLibs.srcDirs = ['jniLibs']//指定lib庫目錄 } }
接着,從新build就會在cpp相同目錄級別位置生成jniLibs目錄,so庫也在其中了:
注意事項: 一、配置了CMAKE_CURRENT_SOURCE_DIR,並不是表示編譯時直接將so生成在該目錄中,實際編譯時,so文件仍然是 先生成在build/.../cmake/中,而後再拷貝到目標目錄中的 二、若是隻配置了CMAKE_CURRENT_SOURCE_DIR,並未在gradle中配置 jniLibs.srcDirs,也會有問題,以下: More than one file was found with OS independent path 'lib/arm64-v8a/libnative-lib.so' 此問題是指:在編譯生成apk時,發現了多個so目錄,android studio不知道使用哪個了,因此須要我們 告訴android studio當前工程使用的是jniLibs目錄,而非build/.../cmake/目錄
咱們能夠在cmake中設置abiFilters,也可在ndk中設置abiFilters,效果是同樣的:
defaultConfig { applicationId "com.qxc.testnativec" minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { cppFlags "" abiFilters "arm64-v8a" } } }
defaultConfig { applicationId "com.qxc.testnativec" minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { cppFlags "" } } ndk { abiFilters "arm64-v8a" } }
按照新的配置,咱們從新運行工程,以下圖:
再反編譯看下工程,果真只有arm64-v8a的so庫了,不過庫文件在apk中仍然是放在lib目錄,而非jniLibs(其實也很好理解,jniLibs只是咱們本地的目錄,便於咱們管理庫文件,真正生成apk時,仍然會按照lib目錄放置庫文件),以下圖:
至此,CMake的主要技術點都講完了,接下來我們看下NDK-Build吧~
在ndk-build編譯以前,我們又應該先作哪些準備工做?
一、ndk-build環境變量是否正確配置? -- 若是未配置,是沒法在cmd、Mac終端、Terminal中使用ndk-build命令的(會報錯:找不到命令) 二、NDK環境是否配置正確? -- 若是未配置正確是沒法進行C/C++開發的,更不用說ndk-build編譯了 三、C/C++功能是否實現? -- 這次演示主要使用系統默認建立的native-lib.cpp文件,關於具體如何實現:後續文章再詳細講解 -- 注意:爲了與CMake區分,我們新建一個「jni」目錄存放C/C++文件、配置文件吧 四、Android.mk是否建立並正確配置? -- 該文件是ndk-build工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果 五、gradle是否正確配置?(可選項,若是經過cmd、Mac終端、Terminal執行ndk-build,可忽略) -- gradle配置也是ndk-build工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果 六、Application.mk是否建立並正確配置?(可選項,通常配ABI、版本,這些項都可在gradle中配置) -- 該文件也是ndk-build工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果
除此以外,我們還應該學習ndk-build的哪些重要知識?
一、ndk-build工具如何指定編譯生成的庫文件位置? 二、ndk-build工具如何指定生成不一樣CPU平臺對應的庫文件?
帶着這些問題,我們繼續ndk-build之旅吧:
介紹NDK-Build定義時,提到了其實它是NDK的腳本工具。那麼,我們仍是先進NDK目錄找一下吧,ndk-build工具的位置以下圖:
若是咱們但願任意狀況下都能便捷的使用這種腳本工具,一般作法是配置其環境變量,不然咱們在cmd、Mac終端、Terminal中執行 ndk-build 命令時,會報錯:「未找到命令」
配置NDK的環境變量,也很簡單,以Mac電腦舉例(若是是Windows電腦,網上也有不少關於配置環境變量的文章,若是有須要可自行查下):
一、打開命令終端,輸入命令: open -e .bash_profile,打開bash_profile配置文件 二、寫入以下內容(NDK_HOME指向 ndk-build 所在路徑): export NDK_HOME=/Users/xc/SDK/android-sdk-macosx/ndk/20.1.5948944 export PATH=$PATH:$NDK_HOME 三、生效.bash_profile配置 source .bash_profile
當咱們在cmd、Mac終端、Terminal中執行 ndk-build 命令時,若是出現下圖所示內容,則表明配置成功了:
我們使用比較經常使用的一種ndk-build方式吧:ndk-build + Android.mk + gradle配置
項目中新建jni目錄,拷貝一份CMake的代碼實現吧:
一、新建jni目錄 二、拷貝cpp/native-lib.cpp 至 jni目錄下 三、重命名爲haha.cpp (與CMake區分) 四、調整一下native實現方法的文本(與CMake運行效果區分) 五、新建Android.mk文件
接着,編寫Android.mk文件內容:
#表示Android.mk所在目錄 LOCAL_PATH := $(call my-dir) #CLEAR_VARS變量指向特殊 GNU Makefile,用於清除部分LOCAL_變量 include $(CLEAR_VARS) #模塊名稱 LOCAL_MODULE := haha #構建系統用於生成模塊的源文件列表 LOCAL_SRC_FILES := haha.cpp #BUILD_SHARED_LIBRARY 表示.so動態庫 #BUILD_STATIC_LIBRARY 表示.a靜態庫 include $(BUILD_SHARED_LIBRARY)
配置gradle:
apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { applicationId "com.aaa.testnative" minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" //定義ndkBuild默認配置屬性 externalNativeBuild { ndkBuild { cppFlags "" } } } //定義ndkBuild對應的Android.mk路徑(重要) externalNativeBuild { ndkBuild{ path "src/main/jni/Android.mk" } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }
如今,native代碼、ndk-build配置都完成了,我們運行看一下效果吧,以下圖:
一般,可在Android.mk文件中配置NDK_APP_DST_DIR
指定源目錄與輸出目錄(與CMake相似)
#表示Android.mk所在目錄 LOCAL_PATH := $(call my-dir) #設置庫文件的輸入目錄 #輸出目錄 ../jniLibs/ #源目錄 $(TARGET_ARCH_ABI) NDK_APP_DST_DIR=../jniLibs/$(TARGET_ARCH_ABI) #CLEAR_VARS變量指向特殊 GNU Makefile,用於清除部分LOCAL_變量 include $(CLEAR_VARS) #模塊名稱 LOCAL_MODULE := haha #構建系統用於生成模塊的源文件列表 LOCAL_SRC_FILES := haha.cpp #BUILD_SHARED_LIBRARY 表示.so動態庫 #BUILD_STATIC_LIBRARY 表示.a靜態庫 include $(BUILD_SHARED_LIBRARY)
可在gradle中配置abiFilters(與Cmake相似)
externalNativeBuild { ndkBuild { cppFlags "" abiFilters "arm64-v8a" } }
externalNativeBuild { ndkBuild { cppFlags "" } } ndk { abiFilters "arm64-v8a" }
除了執行AndroidStudio的build命令,基於gradle配置 + Android.mk編譯生成庫文件,咱們還能夠在cmd、Mac 終端、Terminal中直接經過ndk-build命令構建庫文件,此處以Terminal爲例進行演示吧:
先進入包含Android.mk文件的jni目錄(Android Studio中可直接選中jni目錄並拖拽到Terminal中,會自動跳轉到該目錄),再執行ndk-build命令,以下圖:
一樣,編譯也成功了,以下圖:
因是直接在Terminal中執行了ndk-build命令,因此只會根據Android.mk進行編譯(不包含gradle配置內容,也就不會執行abiFilters過濾),生成了全部默認CPU平臺的so庫文件。
ndk-build命令其實也能夠配上一些參數使用,此處就再也不詳解了。平常開發時,仍是建議選擇CMake做爲Native編譯工具,由於是安卓主推的,並且更簡單一些。