Android Studio 編寫JNI有兩種方式
- 通過(guò)ndk-build編寫奔浅,和eclipse類似馆纳,需要配置Android.mk、Application.mk文件乘凸。之前的一些開(kāi)源庫(kù)還是使用此種方式編寫.so厕诡,因此還是需要了解此種方式。
- 通過(guò)cmake 編寫营勤,Android Studio 2.2(含)之后引入更方便的cmake灵嫌,需要配置CMakeLists.txt。
下面我們就通過(guò)實(shí)例一步步了解這兩種方式異同點(diǎn)葛作。
ndk-build
首先下載ndk寿羞,可以在單獨(dú)下載ndk包,解壓到本地目錄赂蠢,再將工程里配置ndk路徑至解壓的目錄绪穆,或者直接在Android Studio里下載,下載解壓成功后虱岂,自動(dòng)配置路徑玖院,無(wú)需手動(dòng)配置。Android Studio里下載方式如下:
配置路徑:
ndk準(zhǔn)備好后第岖,接下來(lái)開(kāi)始專注于工程本身难菌。
首先創(chuàng)建一個(gè)Android Library Module
創(chuàng)建一個(gè)加法器功能的類:CalNum
public class CalNum {
//暴露給外界的接口
public float testAdd(float a, float b) {
return addFloat(a, b);
}
//通過(guò)jni,調(diào)用c/c++ 函數(shù)
private static native float addFloat(float a, float b);
}
上面的addFloat是本地方法蔑滓,該怎么實(shí)現(xiàn)呢郊酒?我們知道C語(yǔ)言需要一個(gè)頭文件(.h)聲明函數(shù)原型,需要一個(gè)源文件(.c)實(shí)現(xiàn)函數(shù)功能键袱,因此需要?jiǎng)?chuàng)建兩個(gè)文件燎窘。
- 創(chuàng)建頭文件
先了解下我們的工程目錄結(jié)構(gòu)
TestJni/testnum/src/main/java
TestJni是工程名,testnum是Module名蹄咖,java目錄下存放的是純java代碼褐健。
頭文件需要聲明addFloat函數(shù),jni函數(shù)名比較特殊澜汤,通過(guò)javah -jni命令生成铝量。
進(jìn)入Android Studio Terminal,cd 到 java 目錄银亲,執(zhí)行如下命令:
javah -jni com.fish.testnum.CalNum
- com.fish.testnum 是包名
- CalNum 是本地方法所在類的類名
-
javah 命令需要配置java jre環(huán)境變量
該命令成功在java目錄下生成.h文件
.h文件
之前生成的.h文件所在目錄是臨時(shí)的,我們一般會(huì)將c/c++文件放入一個(gè)特定目錄:jni纽匙,因此我們需要?jiǎng)?chuàng)建jni目錄务蝠,右鍵點(diǎn)擊Module:
創(chuàng)建jni目錄
將之前的.h文件拷貝到j(luò)ni目錄下(生成的目錄名為"jni",Android Studio 展示時(shí)為"cpp"烛缔,下面提到的jni等同cpp)馏段。有了.h文件轩拨,現(xiàn)在我們來(lái)編寫.c文件,在jni目錄下創(chuàng)建.c文件院喜,右鍵點(diǎn)擊jni:
創(chuàng)建.c文件
實(shí)現(xiàn).c文件函數(shù)功能:
.c文件功能
該函數(shù)功能實(shí)際就是計(jì)算兩個(gè)數(shù)加結(jié)果亡蓉。
好了,現(xiàn)在已經(jīng)文成.h和.c文件的編寫喷舀,那么如何將c文件編寫為.so文件呢砍濒?這個(gè)時(shí)候就需要借助Android.mk和Application.mk文件了,這兩個(gè)文件通過(guò)特定語(yǔ)法配置一些參數(shù)硫麻,這些文件將決定如何生成一個(gè)makefile文件爸邢,編譯器就會(huì)依據(jù)makefile文件編譯c源文件,最終生成.so文件拿愧。
如何編寫Android.mk文件呢杠河?在jni目錄下新建Android.mk文件:
Android.mk - LOCAL_PATH 指的是當(dāng)前目錄
- include $(CLEAR_VARS) 指的是清空變量
- LOCAL_MODULE := calnum 指的是生成.so文件的名稱,全稱:libcalnum.so
- LOCAL_SRC_FILES 指的是待編譯的源文件
-
include $(BUILD_SHARED_LIBRARY) 指的是生成的庫(kù)類型浇辜,這里是動(dòng)態(tài)庫(kù)
Android.mk 還有其它語(yǔ)法參數(shù)券敌,這里就不展開(kāi)說(shuō)明了。
如何編寫Application.mk文件呢柳洋?在jni目錄下新建Application.mk文件待诅。
Application.mk - APP_ABI 指的是生成哪些cpu架構(gòu)支持的.so文件,all表示所有支持的架構(gòu)膳灶,如果只需要生成某一種或幾種平臺(tái)支持的.so咱士,填相應(yīng)的名字即可,比如x86轧钓、armeabi-v7a等序厉。
ps:經(jīng)測(cè)試,這里無(wú)論怎么填毕箍,都默認(rèn)生成所有支持平臺(tái)的.so弛房。
至此,jni目錄下的文件已經(jīng)準(zhǔn)備齊全:
這時(shí)候我們開(kāi)始make module而柑,然而令人失望的是卻是報(bào)錯(cuò)文捶,原因是我們僅僅準(zhǔn)備了jni相關(guān)文件,編譯器并不知道如何去操作jni文件媒咳,而我們又知道Android.mk記錄這編譯相關(guān)東西粹排,因此應(yīng)當(dāng)先讓編譯器找到Android.mk文件。
在module的build.gradle里涩澡,android層級(jí)內(nèi)顽耳,指定Android.mk位置:
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
這里需要注意的是路徑的確定
"src/main/jni/Android.mk"
build.gradle在app 目錄下,而Android.mk 在/src/main/jni/ 目錄下,因此需要通過(guò)上級(jí)目錄索引到Android.mk
這個(gè)時(shí)候我們?cè)賛ake module射富,成功了膝迎!那么生成的.so文件在哪呢?首先定位到app build/intermediates 目錄下胰耗,搜索".so"文件限次,經(jīng)過(guò)篩選,找到如下目錄:
build/intermediates/ndkBuild/debug/obj/local
該目錄下文件為:
每個(gè)目錄下有對(duì)應(yīng)平臺(tái)的.so庫(kù)柴灯,如下:
我們應(yīng)該注意到了卖漫,這里只是生成了4種平臺(tái)下的.so庫(kù),我們明明記得一般是支持7種平臺(tái)呢弛槐?沒(méi)錯(cuò)懊亡,這里確實(shí)少了 armeabi,mips乎串,mips64平臺(tái)店枣,因?yàn)樵趎dk17開(kāi)始不再支持這三種平臺(tái),而我們這里使用的ndk版本是20叹誉。那么如果想所有平臺(tái)都支持呢鸯两?那么使用的ndk版本需要低于17(經(jīng)過(guò)測(cè)試,ndk16也不行长豁,最好是15及其以下)钧唐。
要查看ndk支持的abi,定位到ndk目錄下匠襟,執(zhí)行ndk-which命令钝侠,即可輸出該版本ndk支持的abi。
上面我們說(shuō)了支持的平臺(tái)不夠多酸舍,但是我們還想減少支持的平臺(tái)數(shù)呢帅韧,這時(shí)候需要在module build.gradle android { {defaultConfig xxx}} 添加如下代碼:
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your APK.
abiFilters "x86", "x86_64", "armeabi-v7a",
"arm64-v8a"
}
想要生成哪個(gè)平臺(tái),填其名字即可啃勉。
現(xiàn)在.so文件已經(jīng)生成忽舟,另一個(gè)模塊如何調(diào)用呢?記得我們創(chuàng)建模塊的時(shí)候是創(chuàng)建了Android Library淮阐,也就是說(shuō)我們module編譯成了.jar文件叮阅,該.jar文件負(fù)責(zé)調(diào)用.so文件里的函數(shù),并且.jar文件暴露給外界模塊接口泣特,外界模塊間接調(diào)用了.so浩姥,整個(gè)流程下來(lái)就完成了一個(gè)最簡(jiǎn)單的jni編程、實(shí)例調(diào)用状您。那具體怎么配置module調(diào)用呢勒叠?有兩種方法:
1镀裤、調(diào)用者工程內(nèi)直接依賴Android Library module,優(yōu)點(diǎn)是方便調(diào)試缴饭,前提是我們有Android Library module源碼
2、 調(diào)用者工程內(nèi)依賴.jar包骆莹,現(xiàn)成的第三方庫(kù)一般以jar包形式提供颗搂。
下面分別簡(jiǎn)要說(shuō)明兩者的配置方式
-
直接依賴module:
選擇依賴
選中Module dependency,選擇需要依賴的module幕垦,確定后丢氢,再到調(diào)用者的module build.gradle里查看:
依賴module
最后一項(xiàng)就是之前界面操作的結(jié)果。 -
間接依賴module:
先找到j(luò)ar包先改,定位到libary module app/build/intermediates 目錄下疚察,搜索".jar",
最后定位到:build/intermediates/packaged-classes/debug 目錄下的class.jar仇奶,就是我們要找的jar包貌嫡,可以將之改為比較好記名字,這里改為calnum.jar该溯,將該文件拷貝至調(diào)用者module app/lib目錄下岛抄,再在調(diào)用者module里引入該.jar包,依然可以通過(guò)界面操作依賴:
選擇依賴
選中jar dependency狈茉,選擇需要依賴的jar夫椭,確定后,再到調(diào)用者的module build.gradle里查看:
依賴jar
最后一項(xiàng)就是之前界面操作的結(jié)果氯庆。
jar包依賴已經(jīng)搞定蹭秋,還有so庫(kù)呢?也是有兩種方式堤撵,對(duì)應(yīng)上面和兩種依賴jar包方式: - 直接依賴module
這種方式下不用配置so庫(kù)位置 -
間接依賴module
定位到調(diào)用者module src/main 目錄下仁讨,新建文件夾,名為:"jniLibs“粒督,然后將之前生成的各個(gè)平臺(tái)的so庫(kù)放入該文件夾下:
jniLibs
當(dāng)然陪竿,如果不想放在該目錄下,也可以和jar包一起放在"libs"文件夾下屠橄,前提是需要在build.gradle android 層級(jí)下指明so庫(kù)的位置
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
至此族跛,jar包和so庫(kù)都準(zhǔn)備好了,調(diào)用者就可以調(diào)用暴露出來(lái)的接口進(jìn)行訪問(wèn)了锐墙。
btnNdk.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CalNum calNum = new CalNum();
String toast = calNum.testAdd(20, 30) + " nukbuild";
Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show();
}
});
其中calNum 類就是jar包種的類
cmake
通過(guò)上面ndk-build方式可知礁哄,需要我們配置Android.mk Application.mk文件,比較繁瑣溪北。Google為此推出了新的編譯方式-cmake夺脾,那么cmake需要怎么做呢?
首先下載cmake工具
其次茉继,新建project的時(shí)候咧叭,會(huì)有一個(gè)c++支持選項(xiàng),勾選即可烁竭。project創(chuàng)建完畢后菲茬,會(huì)發(fā)現(xiàn)比沒(méi)勾選時(shí)多了幾個(gè)文件:
1、src/main 目錄下新建了jni文件夾派撕,并且預(yù)先放置了一個(gè)cpp文件:
和ndk-build jni目錄一致的婉弹,當(dāng)然也可以放.c 和 .h文件。
2终吼、app 目錄下多了個(gè)CMakeLists.txt镀赌,該文件的作用和ndk-build時(shí)使用的.mk文件類似。
我們來(lái)看看該文件里邊的語(yǔ)法:
- add_library
native-lib 指的是要生成的庫(kù)名稱
SHARED 指的是生成的庫(kù)為動(dòng)態(tài)庫(kù)
src/main/cpp/native-lib.cpp 指的是需要編譯的源文件 - find_library 配置的是需要依賴的外部庫(kù)
-
target_link_libraries 配置的是最終將多個(gè)庫(kù)鏈接起來(lái)
3际跪、build.gradle 新增了幾項(xiàng)配置:
build.gradle
配置 cmake 編譯參數(shù)等商佛。
externalNativeBuild {
cmake {
cppFlags ""
}
}
讓編譯器知道CMakeLists.txt 位置
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
注:第三點(diǎn)是手動(dòng)新增的,新建的project并沒(méi)有垫卤,編譯會(huì)報(bào)錯(cuò)威彰,加上第三點(diǎn)解決編譯報(bào)錯(cuò)問(wèn)題。
至此穴肘,cmake方式編譯jni配置工作就完成了歇盼,只需要簡(jiǎn)單的勾選就可以支持ndk編程,是不是覺(jué)得比之前方便多了评抚。也許你會(huì)問(wèn)豹缀,創(chuàng)建工程時(shí)忘了添加c++支持,后面有需要編譯jni怎么辦呢慨代?還是按照上面的方法邢笙,手動(dòng)添加:
1、下載cmake工具
2侍匙、新建jni氮惯,編寫.c/.h 、c++源文件
3想暗、新建CMakeLists.txt妇汗,配置其中參數(shù)
4、配置build.gradle
ndk-build方式和cmake方式編寫簡(jiǎn)單入門jni程序已經(jīng)梳理完畢说莫,總結(jié)幾個(gè)比較關(guān)鍵的點(diǎn):
1杨箭、兩種方式需要哪些配置文件容易搞混亂(相對(duì)來(lái)說(shuō),具體配置文件語(yǔ)法都可以查得到储狭,反而比較簡(jiǎn)單)
2互婿、配置時(shí)路徑容易搞糊涂捣郊,實(shí)際上只要厘清當(dāng)前配置文件所在的位置、待指向的配置的文件所在的位置慈参,相對(duì)位置就整明白了呛牲,進(jìn)而填上相對(duì)索引即可訪問(wèn)
最后,整個(gè)效果圖:
本文基于Android Studio 3.2 NDK 20