之前對NDK開發(fā)一直是個(gè)小白往湿,最近花了幾天時(shí)間研究得到的一些理解在此做個(gè)記錄分享妖异。結(jié)論不足之處拒絕反駁惨好,所有觀點(diǎn)僅單方面宣布,后果自負(fù)随闺。*.*,本文出處:http://www.reibang.com/p/201046751a7c
一蔓腐、什么是NDK矩乐?
NDK全稱是Native Development Kit(原生開發(fā)工具包),NDK提供了一系列的工具回论,幫助開發(fā)者快速開發(fā)C(或C++)的動態(tài)庫散罕,并能自動將so和java應(yīng)用一起打包成apk。也就是說它是一個(gè)“開發(fā)工具包”傀蓉,就像SDK一樣欧漱,區(qū)別就在于SDK是面向java開發(fā)者的工具集合,而NDK面向的則是C/C++開發(fā)者的工具集合(包括對c/c++源碼的打包編譯工具ndk葬燎,一些h頭文件等)误甚。附上官方NDK工具包的下載路徑:官網(wǎng)ndk下載,需要翻墻谱净。
二窑邦、為什么需要使用NDK?
1.代碼的保護(hù)壕探。由于apk的java層代碼很容易被反編譯冈钦,而C/C++庫反匯難度較大。
2.可以方便地使用現(xiàn)存的開源庫李请。大部分現(xiàn)存的開源庫都是用C/C++代碼編寫的瞧筛。
3.提高程序的執(zhí)行效率。將要求高性能的應(yīng)用邏輯使用C開發(fā)导盅,從而提高應(yīng)用程序的執(zhí)行效率较幌。
4.便于移植。用C/C++寫得庫可以方便在其他的嵌入式平臺上再次使用认轨。
三绅络、JNI、SO介紹
JNI 全稱Java Native Interface嘁字,這套技術(shù)的機(jī)制是用于java訪問c/c++代碼而產(chǎn)生的恩急,說白了NDK開發(fā)的核心就是JNI開發(fā),利用java代碼來調(diào)用遵循JNI規(guī)范的c/c++的方法實(shí)現(xiàn)某個(gè)功能纪蜒。
so全稱Shared Object衷恭,本地原生庫,先暫時(shí)理解為java中的jar包纯续,所有的c/c++的代碼在android(Linux)平臺中最終都會編譯成so庫随珠,然后才能被調(diào)用灭袁。所以ndk開發(fā)所編寫出的c/c++代碼最終的目的都是為了獲得這個(gè)so庫,與java方法形成jni的映射關(guān)系從而實(shí)現(xiàn)調(diào)用窗看。
四乙各、開始擼碼
本文使用Android Studio2.0進(jìn)行演示HelloJni
? 大概步驟:
? ? ?1. java文件中聲明native方法翻具,和java方法聲明一樣,在此基礎(chǔ)上加了natvie修飾。
? ? ?2. 利用javah命令生成與該類對應(yīng)的頭文件(包含方法信息)
? ? ?3. 根據(jù)頭文件的信息編寫c源代碼文件
? ? ?4. 在app\build.gradle文件中配置ndk的編譯信息
? ? ?5. 配置NDK工具包路徑级乐,編譯運(yùn)行
創(chuàng)建項(xiàng)目:HelloJni
1. 定義一個(gè)java類SayHello迈着,并在里面聲明一個(gè)靜態(tài)無參native方法speak竿拆,并且創(chuàng)建jni文件夾
2. 利用javah命令生成與該類對應(yīng)的jni頭文件挑辆,生成的頭文件的目的主要是用來編寫c/c++源文件
3.根據(jù).h頭文件的信息編寫c源代碼文件 : 創(chuàng)建SayHello.c文件,把頭文件里的方法copy到該文件中魔慷,并修改成實(shí)體方法只锭,下面則是返回一段字符串。如果熟悉了jni方法名稱命名規(guī)范院尔,完全可自己手寫蜻展,生成頭文件的步驟也可跳過。親測發(fā)現(xiàn)如果包名帶有數(shù)字的命名規(guī)則不好把握邀摆,所以建議用javah生成铺呵。
4.在build.gradle中配置ndk的編譯信息,配置完成保存同步之后可能出現(xiàn)錯(cuò)誤隧熙,添加 android.useDeprecatedNdk=true 到gradle.properties 文件中即可解決片挂。
5.配置下載好的NDK工具包:File->Project Structure->SDK Location(文件路徑\android-ndk-r14b目錄配置到系統(tǒng)環(huán)境變量中,以備后面使用)
然后回到在java文件中贞盯,加載buil.gradle中配置的moduleName的類庫名稱音念,這里配置為:SayHello
最后在MainActivity中測試該方法。
運(yùn)行躏敢。
至此闷愤,體驗(yàn)了一把基本的ndk開發(fā)過程。不過洗腦還沒有結(jié)束:
在運(yùn)行完成之后件余,我們并沒有發(fā)現(xiàn)工程目錄中有so庫文件讥脐,其實(shí)這個(gè)so庫文件是在運(yùn)行之后直接打包到了apk文件中的lib目錄下了
由于我們在build.gradle配置了abiFilters打包時(shí)只打包x86的文件夾中的so庫。所以我們在apk中只看到x86的文件夾啼器,里面存放的就是so庫旬渠,如果不配置abiFilters,那么將會出現(xiàn)android支持的7種abi端壳,可參見該文章理解ABI告丢。
因?yàn)槲覀兛梢哉{(diào)用so這個(gè)庫,顯然這個(gè)so庫是根據(jù)我們在jni文件夾下編寫的源文件編譯生成的损谦,如果我們沒有配置岖免,gradle默認(rèn)就會去編譯jni的文件夾下的c/c++的代碼生成so庫岳颇,這個(gè)路勁就是src/main/jni,如果這個(gè)文件夾沒有文件即使配置了ndk{...}信息也不會生成so庫颅湘,當(dāng)然gradle還提供自定義配置话侧,下面就看看如何配置:
sourceSets{ ?main{ ? ?jin.srcDirs=["src/mian/jni"] ?//默認(rèn)路徑,jin.srcDirs指的是需要加入編譯的jni的路徑闯参,可以自己修改路徑的 ?} }
這個(gè)apk安裝到x86 abi手機(jī)上之后掂摔,so庫會安裝在data\app\包名-數(shù)值\ib目錄下(可通過Device Monitor工具查看),所以由此可判斷System.loadLibrary()加載的庫默認(rèn)是這個(gè)路徑下的庫赢赊,也可以調(diào)用System.load(data\app\包名-數(shù)值\ib\abi\libxx.so)加載絕對路徑的so庫。
所以java代碼能不能正確的執(zhí)行so庫里的內(nèi)容取決于so庫能否被正確的安裝级历。如果未能正確安裝释移,當(dāng)虛擬機(jī)去System.loadLibrary時(shí)就會報(bào)錯(cuò)java.lang.UnsatisfiedLinkError。
上面的做法只有在打包時(shí)才能得到so庫寥殖,下面就介紹通過ndk開發(fā)工具包里的ndk-build單獨(dú)來編譯出so庫玩讳,這種方式就無需在build.gradle中配置ndk{...}了。
1. 在jni文件家中新建android.mk編譯配置文件嚼贡,參見Android.mk詳細(xì)配置
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE ? ?:= SayHello
LOCAL_SRC_FILES := SayHello.c
include $(BUILD_SHARED_LIBRARY)
2.在jni文件家中新建application.mk配置需要生成支持的abi so庫熏纯,參見Application.mk詳細(xì)配置
APP_CFLAGS += -Wno-error=format-security
APP_ABI := all
這里配置支持所有的abi。
3.在terminal中調(diào)用ndk-build工具生成so庫
我們可以看到在main文件夾下生成了libs目錄粤策,并且生成了支持所有abi的so庫樟澜,到此生成so完畢;現(xiàn)在任務(wù)就是要讓這些so庫打包到apk文件中的libs目錄下叮盘,在build.gradle中配置sourceSets的另一個(gè)屬性jniLibs.srcDirs秩贰,配置的路徑下的so庫文件都會打包到apk文件中,其默認(rèn)值為app/libs柔吼,所以也可以把這些so文件拷到app/libs中而不配置這個(gè)屬性用其默認(rèn)值毒费。
上圖中不配置jni.srcDirs的路徑的作用是為了打包時(shí)不讓編譯系統(tǒng)再去編譯得到so庫(因?yàn)槲覀円呀?jīng)單獨(dú)生成),雖然上面ndk沒有被配置愈魏,但是只要的配置這個(gè)路徑下有c文件就會生成so庫觅玻,并且名字為libapp.so,這樣一來就造成了相同的包存在兩個(gè)增加app的體積培漏。
最后build apk看看apk里有沒有so庫:
運(yùn)行溪厘,大功告成。
下一篇文章介紹:第三方so庫的調(diào)用