1盖腕、基礎(chǔ)概念
├──1.1、JNI
├──1.2浓镜、NDK
├──1.3溃列、CMake與ndk-build
2、環(huán)境搭建
3膛薛、Native C++ 項(xiàng)目(HelloWord案例)
├── 3.1听隐、項(xiàng)目創(chuàng)建(java、kotlin)
├── 3.2相叁、CMake的應(yīng)用(詳細(xì)講解)
├── 3.3遵绰、ndk-build的應(yīng)用(詳細(xì)講解)
1、基礎(chǔ)概念
1.1增淹、JNI
JNI(Java Native Interface)Java本地接口椿访,使得Java與C/C++具有交互能力
1.2、NDK
NDK(Native Development Kit) 本地開發(fā)工具包虑润,允許使用原生語言(C和C++)來實(shí)現(xiàn)應(yīng)用程序的部分功能
Android NDK開發(fā)的主要作用:
1成玫、特定場景下,提升應(yīng)用性能;
2哭当、代碼保護(hù)猪腕,增加反編譯難度;
3钦勘、生成庫文件陋葡,庫可重復(fù)使用,也便于平臺彻采、項(xiàng)目間移植腐缤;
1.3、CMake與ndk-build
當(dāng)我們基于NDK開發(fā)出native功能后肛响,通常需要編譯成庫文件岭粤,給Android項(xiàng)目使用。
目前特笋,有兩種主流的編譯方式:CMake與ndk-build
CMake與ndk-build是兩種不同的編譯工具(與Android代碼和C/C++代碼無關(guān))
CMake
CMake是Androidstudio2.2之后引入的跨平臺編譯工具(特點(diǎn):簡單易用剃浇,2.2之后是默認(rèn)的NDK編譯工具)
如何配置:
1、創(chuàng)建CMakeLists.txt文件猎物,配置CMake必要參數(shù)虎囚;
2、使用gradle配置CMakeLists.txt以及native相關(guān)參數(shù)霸奕;
如何編譯庫文件:
1溜宽、Android Studio執(zhí)行Build即可;
ndk-build
ndk-build是NDK中包含的腳本工具(可在NDK目錄下找到該工具质帅,為了方便使用适揉,通常配置NDK的環(huán)境變量)
如何配置:
1、創(chuàng)建Android.mk文件煤惩,配置ndk-build必要參數(shù)嫉嘀;
2、可選創(chuàng)建application.mk文件魄揉,配置ndk-build參數(shù) (該文件的配置項(xiàng)可使用gradle的配置替代)剪侮;
3、使用gradle配置Android.mk以及native相關(guān)參數(shù)洛退;
2瓣俯、如何編譯庫文件(兩種方式):
1、Android Studio執(zhí)行Build即可(執(zhí)行了:Android.mk + gradle配置)兵怯;
2彩匕、也可在Terminal、Mac終端媒区、cmd終端中通過ndk-build命令直接構(gòu)建庫文件(執(zhí)行了:Android.mk)
2驼仪、環(huán)境搭建
JNI安裝
JNI 是JDK里的內(nèi)容掸犬,電腦上正確安裝并配置JDK即可 (JDK1.1之后就正式支持了);
NDK安裝
可從官網(wǎng)自行下載、解壓到本地绪爸,也可基于AndroidStudio下載解壓到默認(rèn)目錄;
編譯工具安裝
cmake 可基于AndroidStudio下載安裝;
ndk-build 是NDK里的腳本工具湾碎,NDK安裝好即可使用ndk-build;
當(dāng)前演示,使用的Android Studio版本如下(當(dāng)前最新版):
啟動Android Studio --> 打開SDK Manager --> SDK Tools奠货,如下圖所示:
我們選擇NDK介褥、CMake、LLDB(調(diào)試Native時(shí)才會使用)仇味,選擇Apply進(jìn)行安裝呻顽,等安裝成功后雹顺,NDK開發(fā)所依賴的環(huán)境也就都齊全了丹墨。
3、Native C++ 項(xiàng)目(HelloWord案例)
3.1嬉愧、項(xiàng)目創(chuàng)建(java / kotlin)
新建項(xiàng)目贩挣,選擇 Native C++,如下圖:
新創(chuàng)建的項(xiàng)目没酣,默認(rèn)已包含完整的native 示例代碼王财、cmake配置 ,如下圖:
這樣裕便,我們就可以自己定義Java native方法绒净,并在cpp目錄中寫native實(shí)現(xiàn)了,很方便偿衰。
但是挂疆,當(dāng)我們寫完native的實(shí)現(xiàn)代碼,希望運(yùn)行APP下翎,查看JNI的交互效果缤言,此時(shí),就需要使用編譯工具了视事,咱們還是先看一下Android Studio默認(rèn)的Native編譯方式吧:CMake
3.2胆萧、CMake的應(yīng)用
在CMake編譯之前,咱們應(yīng)該先做哪些準(zhǔn)備工作俐东?
1跌穗、NDK環(huán)境是否配置正確?
-- 如果未配置正確是無法進(jìn)行C/C++開發(fā)的虏辫,更不用說CMake編譯了
2蚌吸、C/C++功能是否實(shí)現(xiàn)?
-- 此次演示主要使用系統(tǒng)默認(rèn)創(chuàng)建的native-lib.cpp文件乒裆,關(guān)于具體如何實(shí)現(xiàn):后續(xù)文章再詳細(xì)講解
3套利、CMakeLists.txt是否創(chuàng)建并正確配置推励?
-- 該文件是CMake工具編譯的基礎(chǔ),未配置或配置項(xiàng)錯(cuò)誤肉迫,均會影響編譯結(jié)果
4验辞、gradle是否正確配置?
-- gradle配置也是CMake工具編譯的基礎(chǔ)喊衫,未配置或配置項(xiàng)錯(cuò)誤跌造,均會影響編譯結(jié)果
除此之外,咱們還應(yīng)該學(xué)習(xí)CMake的哪些重要知識族购?
1壳贪、CMake工具編譯生成的庫文件默認(rèn)在什么位置?apk中庫文件又是在什么位置寝杖?
2违施、CMake工具如何指定編譯生成的庫文件位置?
3瑟幕、CMake工具如何指定生成不同CPU平臺對應(yīng)的庫文件磕蒲?
帶著這些問題,咱們開始CMake之旅吧:
3.2.1只盹、NDK環(huán)境檢查
編譯前辣往,建議先檢查下工程的NDK配置情況(不然容易報(bào)一些亂七八糟的錯(cuò)誤):
File --> Project Structure --> SDK Location,如下圖(我本地的Android Studio默認(rèn)沒有給配置NDK路徑殖卑,那么站削,需要自己手動指定一下):
3.2.2、C/C++功能實(shí)現(xiàn)
因?yàn)楸竟?jié)主講CMake編譯工具孵稽,代碼就不單獨(dú)寫了许起,咱們直接使用工程默認(rèn)生成的native-liv.cpp,簡單調(diào)整一下native實(shí)現(xiàn)方法的代碼吧(修改返回文本信息):
因Native C++工程默認(rèn)已配置好了CMakeLists.txt和gradle肛冶,所以咱們可直接運(yùn)行工程看效果街氢,如下圖:
JNI交互效果我們已經(jīng)看到了,說明CMake編譯成功了睦袖。那么珊肃,這究竟是怎么做到的呢?咱們接著分析一下吧:
3.2.3馅笙、CMake生成的庫文件與apk中的庫文件
安卓工程編譯時(shí)伦乔,會執(zhí)行CMake編譯,在 工程/app/build/.../cmake/ 中會產(chǎn)生對應(yīng)的so文件董习,如下圖:
繼續(xù)編譯安卓工程烈和,會根據(jù)build中的內(nèi)容,生成我們的*.apk安裝包文件皿淋。我們找到招刹、反編譯apk安裝包文件恬试,查找so庫文件。原來在apk安裝包中疯暑,so庫都被存放在lib目錄中训柴,如下圖:
3.2.4、CMake是如何編譯生成so庫的呢妇拯?
在前面介紹CMake定義時(shí)幻馁,提到了CMake是基于CMakeLists.txt文件和gradle配置實(shí)現(xiàn)編譯Native類的。那么越锈,咱們先來看一下CMakeLists.txt文件吧:
#cmake最低版本要求
cmake_minimum_required(VERSION 3.4.1)
#添加庫
add_library(
# 庫名
native-lib
# 類型:
# SHARED 是指動態(tài)庫仗嗦,對應(yīng)的是.so文件
# STATIC 是指靜態(tài)庫,對應(yīng)的是.a文件
# 其他類型:略
SHARED
# native類路徑
native-lib.cpp)
# 查找依賴庫
find_library(
# 依賴庫別名
log-lib
# 希望加到本地的NDK庫名稱甘凭,log指NDK的日志庫
log)
# 鏈接庫稀拐,建立關(guān)系( 此處就是指把log-lib 鏈接給 native-lib使用 )
target_link_libraries(
# 目標(biāo)庫名稱(native-lib 是咱們要生成的so庫)
native-lib
# 要鏈接的庫(log-lib 是上面查找的log庫)
${log-lib})
實(shí)際上,CMakeList.txt可配置的內(nèi)容遠(yuǎn)不止這些对蒲,如:so輸出目錄钩蚊,生成規(guī)則等等,有需要的同學(xué)可查下官網(wǎng)蹈矮。
接著,咱們再看一下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默認(rèn)配置屬性
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
//定義cmake對應(yīng)的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'
}
實(shí)際上泛鸟,gradle可配置的cmake內(nèi)容也遠(yuǎn)不止這些,如:abi踊东、cppFlags北滥、arguments等,有需要的同學(xué)可查下官網(wǎng)闸翅。
3.2.5再芋、如何指定庫文件的輸出目錄?
如果希望將so庫生成到特定目錄坚冀,并讓項(xiàng)目直接使用該目錄下的so济赎,應(yīng)該如何配置呢?
比較簡單:需要在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 生成庫文件時(shí),采用gradle配置的ABI策略(即:生成哪些平臺對應(yīng)的庫文件)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
# 添加庫
add_library( # 庫名
native-lib
# 類型:
# SHARED 是指動態(tài)庫液南,對應(yīng)的是.so文件
# STATIC 是指靜態(tài)庫壳猜,對應(yīng)的是.a文件
# 其他類型:略
SHARED
# native類路徑
native-lib.cpp)
# 查找依賴庫
find_library(
# 依賴庫別名
log-lib
# 希望加到本地的NDK庫名稱,log指NDK的日志庫
log)
# 鏈接庫滑凉,建立關(guān)系( 此處就是指把log-lib 鏈接給native-lib使用 )
target_link_libraries(
# 目標(biāo)庫名稱(native-lib就是咱們要生成的so庫)
native-lib
# 要鏈接的庫(上面查找的log庫)
${log-lib})
還需要在gradle中配置 jniLibs.srcDirs 屬性(即:指定了lib庫目錄):
sourceSets {
main {
jniLibs.srcDirs = ['jniLibs']//指定lib庫目錄
}
}
接著统扳,重新build就會在cpp相同目錄級別位置生成jniLibs目錄喘帚,so庫也在其中了:
注意事項(xiàng):
1、配置了CMAKE_CURRENT_SOURCE_DIR咒钟,并非表示編譯時(shí)直接將so生成在該目錄中啥辨,實(shí)際編譯時(shí),so文件仍然是
先生成在build/.../cmake/中盯腌,然后再拷貝到目標(biāo)目錄中的
2溉知、如果只配置了CMAKE_CURRENT_SOURCE_DIR,并未在gradle中配置 jniLibs.srcDirs腕够,也會有問題级乍,如下:
More than one file was found with OS independent path 'lib/arm64-v8a/libnative-lib.so'
此問題是指:在編譯生成apk時(shí),發(fā)現(xiàn)了多個(gè)so目錄帚湘,android studio不知道使用哪一個(gè)了玫荣,所以需要咱們
告訴android studio當(dāng)前工程使用的是jniLibs目錄,而非build/.../cmake/目錄
3.2.5、如何生成指定CPU平臺對應(yīng)的庫文件呢横蜒?
我們可以在cmake中設(shè)置abiFilters探橱,也可在ndk中設(shè)置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"
}
}
按照新的配置焙贷,我們重新運(yùn)行工程,如下圖:
再反編譯看下工程贿堰,果然只有arm64-v8a的so庫了辙芍,不過庫文件在apk中仍然是放在lib目錄,而非jniLibs(其實(shí)也很好理解羹与,jniLibs只是我們本地的目錄故硅,便于我們管理庫文件,真正生成apk時(shí)纵搁,仍然會按照lib目錄放置庫文件)吃衅,如下圖:
至此,CMake的主要技術(shù)點(diǎn)都講完了腾誉,接下來咱們看下NDK-Build吧~
3.3徘层、ndk-build的應(yīng)用
在ndk-build編譯之前,咱們又應(yīng)該先做哪些準(zhǔn)備工作妄辩?
1惑灵、ndk-build環(huán)境變量是否正確配置?
-- 如果未配置眼耀,是無法在cmd英支、Mac終端、Terminal中使用ndk-build命令的(會報(bào)錯(cuò):找不到命令)
2哮伟、NDK環(huán)境是否配置正確干花?
-- 如果未配置正確是無法進(jìn)行C/C++開發(fā)的妄帘,更不用說ndk-build編譯了
3、C/C++功能是否實(shí)現(xiàn)池凄?
-- 此次演示主要使用系統(tǒng)默認(rèn)創(chuàng)建的native-lib.cpp文件抡驼,關(guān)于具體如何實(shí)現(xiàn):后續(xù)文章再詳細(xì)講解
-- 注意:為了與CMake區(qū)分,咱們新建一個(gè)“jni”目錄存放C/C++文件肿仑、配置文件吧
4致盟、Android.mk是否創(chuàng)建并正確配置?
-- 該文件是ndk-build工具編譯的基礎(chǔ)尤慰,未配置或配置項(xiàng)錯(cuò)誤馏锡,均會影響編譯結(jié)果
5、gradle是否正確配置伟端?(可選項(xiàng)杯道,如果通過cmd、Mac終端责蝠、Terminal執(zhí)行ndk-build党巾,可忽略)
-- gradle配置也是ndk-build工具編譯的基礎(chǔ),未配置或配置項(xiàng)錯(cuò)誤霜医,均會影響編譯結(jié)果
6齿拂、Application.mk是否創(chuàng)建并正確配置?(可選項(xiàng)支子,一般配ABI创肥、版本,這些項(xiàng)均可在gradle中配置)
-- 該文件也是ndk-build工具編譯的基礎(chǔ)值朋,未配置或配置項(xiàng)錯(cuò)誤,均會影響編譯結(jié)果
除此之外巩搏,咱們還應(yīng)該學(xué)習(xí)ndk-build的哪些重要知識昨登?
1、ndk-build工具如何指定編譯生成的庫文件位置贯底?
2丰辣、ndk-build工具如何指定生成不同CPU平臺對應(yīng)的庫文件?
帶著這些問題禽捆,咱們繼續(xù)ndk-build之旅吧:
3.3.1笙什、環(huán)境變量配置
介紹ndk-build定義時(shí),提到了其實(shí)它是NDK的腳本工具胚想。那么琐凭,咱們還是先進(jìn)NDK目錄找一下吧,ndk-build工具的位置如下圖:
如果我們希望任意情況下都能便捷的使用這種腳本工具浊服,通常做法是配置其環(huán)境變量统屈,否則我們在cmd胚吁、Mac終端、Terminal中執(zhí)行 ndk-build 命令時(shí)愁憔,會報(bào)錯(cuò):“未找到命令”
配置NDK的環(huán)境變量腕扶,也很簡單,以Mac電腦舉例(如果是Windows電腦吨掌,網(wǎng)上也有很多關(guān)于配置環(huán)境變量的文章半抱,如果有需要可自行查下):
1、打開命令終端膜宋,輸入命令: open -e .bash_profile窿侈,打開bash_profile配置文件
2、寫入如下內(nèi)容(NDK_HOME指向 ndk-build 所在路徑):
export NDK_HOME=/Users/xc/SDK/android-sdk-macosx/ndk/20.1.5948944
export PATH=$PATH:$NDK_HOME
3激蹲、生效.bash_profile配置
source .bash_profile
當(dāng)我們在cmd棉磨、Mac終端、Terminal中執(zhí)行 ndk-build 命令時(shí)学辱,如果出現(xiàn)下圖所示內(nèi)容乘瓤,則代表配置成功了:
3.3.2、C/C++功能實(shí)現(xiàn)
咱們先完成原生代碼實(shí)現(xiàn)策泣,然后再進(jìn)行ndk-build編譯衙傀,看下運(yùn)行效果(此處就使用比較常用的一種ndk-build方式吧:ndk-build + Android.mk + gradle配置)
為了突出演示效果,咱們在項(xiàng)目中新建jni目錄萨咕,拷貝一份CMake的代碼實(shí)現(xiàn):
1统抬、新建jni目錄
2、拷貝cpp/native-lib.cpp 至 jni目錄下
3危队、重命名為haha.cpp (與CMake區(qū)分)
4聪建、調(diào)整一下native實(shí)現(xiàn)方法的文本(與CMake運(yùn)行效果區(qū)分)
5、新建Android.mk文件
代碼實(shí)現(xiàn)已經(jīng)完成了茫陆,下面開始配置ndk-build吧:
編寫Android.mk文件內(nèi)容金麸,如下:
#表示Android.mk所在目錄
LOCAL_PATH := $(call my-dir)
#CLEAR_VARS變量指向特殊 GNU Makefile,用于清除部分LOCAL_變量
include $(CLEAR_VARS)
#模塊名稱
LOCAL_MODULE := haha
#構(gòu)建系統(tǒng)用于生成模塊的源文件列表
LOCAL_SRC_FILES := haha.cpp
#BUILD_SHARED_LIBRARY 表示.so動態(tài)庫
#BUILD_STATIC_LIBRARY 表示.a靜態(tài)庫
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默認(rèn)配置屬性
externalNativeBuild {
ndkBuild {
cppFlags ""
}
}
}
//定義ndkBuild對應(yīng)的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'
}
現(xiàn)在挥下,native代碼、ndk-build配置都完成了桨醋,咱們運(yùn)行看一下效果吧棚瘟,如下圖:
3.3.4、如何指定庫文件的輸出目錄喜最?
通常偎蘸,可在Android.mk文件中配置NDK_APP_DST_DIR
指定源目錄與輸出目錄(與CMake類似)
#表示Android.mk所在目錄
LOCAL_PATH := $(call my-dir)
#設(shè)置庫文件的輸入目錄
#輸出目錄 ../jniLibs/
#源目錄 $(TARGET_ARCH_ABI)
NDK_APP_DST_DIR=../jniLibs/$(TARGET_ARCH_ABI)
#CLEAR_VARS變量指向特殊 GNU Makefile,用于清除部分LOCAL_變量
include $(CLEAR_VARS)
#模塊名稱
LOCAL_MODULE := haha
#構(gòu)建系統(tǒng)用于生成模塊的源文件列表
LOCAL_SRC_FILES := haha.cpp
#BUILD_SHARED_LIBRARY 表示.so動態(tài)庫
#BUILD_STATIC_LIBRARY 表示.a靜態(tài)庫
include $(BUILD_SHARED_LIBRARY)
3.3.5、如何生成指定CPU平臺對應(yīng)的庫文件呢禀苦?
可在gradle中配置abiFilters(與Cmake類似)
externalNativeBuild {
ndkBuild {
cppFlags ""
abiFilters "arm64-v8a"
}
}
externalNativeBuild {
ndkBuild {
cppFlags ""
}
}
ndk {
abiFilters "arm64-v8a"
}
3.3.6蔓肯、如何在Terminal中直接通過ndk-build命令構(gòu)建庫文件呢?
除了執(zhí)行AndroidStudio的build命令振乏,基于gradle配置 + Android.mk編譯生成庫文件蔗包,我們還可以在cmd、Mac 終端慧邮、Terminal中直接通過ndk-build命令構(gòu)建庫文件调限,此處以Terminal為例進(jìn)行演示吧:
先進(jìn)入包含Android.mk文件的jni目錄(Android Studio中可直接選中jni目錄并拖拽到Terminal中,會自動跳轉(zhuǎn)到該目錄)误澳,再執(zhí)行ndk-build命令耻矮,如下圖:
同樣,編譯也成功了忆谓,如下圖:
因是直接在Terminal中執(zhí)行了ndk-build命令裆装,所以只會根據(jù)Android.mk進(jìn)行編譯(不包含gradle配置內(nèi)容,也就不會執(zhí)行abiFilters過濾)倡缠,生成了所有默認(rèn)CPU平臺的so庫文件哨免。
ndk-build命令其實(shí)也可以配上一些參數(shù)使用,此處就不再詳解了昙沦。日常開發(fā)時(shí)琢唾,還是建議選擇CMake作為Native編譯工具,因?yàn)槭前沧恐魍频亩芤腋唵我恍?/p>
第一篇:安卓JNI精細(xì)化講解采桃,讓你徹底了解JNI(一):環(huán)境搭建與HelloWord
http://www.reibang.com/p/42afbd9f0bd7