? ? ? 在探索android JNI的路途上真的踩了不少大坑,網(wǎng)上找的很多資料也解決不了凉翻,后來總算解決了了讨,一個是我開發(fā)使用的android studio已經(jīng)在2.2以上版本,由于一些方式的改變導(dǎo)致了有這些新坑制轰;其次還有網(wǎng)上說的方式是之前版本ide支持的方式但是很多沒有備注前计,所以解決不了我的問題。現(xiàn)在把我遇到的坑以及解決的方式記錄下來垃杖,免得有人再去踩坑男杈,因為查到的資料太少,我所使用的方式不一定是最佳路徑调俘,只是希望能暫時先提供一種填坑的方式伶棒。
(本文適合初接觸JNI的coder)
一、什么是JNI和NDK
? ? ? ?在工作接觸了一段時間android開發(fā)的android coder應(yīng)該都會知道彩库,android上的很多功能往往不是應(yīng)用層(也就是java層)實現(xiàn)的肤无,很多功能都是所謂的“底層”實現(xiàn),我們在開發(fā)時通過應(yīng)用層調(diào)用“底層”提供的接口骇钦,如果進(jìn)一步探索宛渐,就會發(fā)現(xiàn)“底層”使用c/c++實現(xiàn)的。剛剛接觸這類事物的coder往往也很不習(xí)慣司忱,為什么要這么弄皇忿,so文件又是啥,NDK又是啥為什么要配置坦仍,其實這就是我們所說的JNI鳍烁,就此來解釋JNI和NDK:
JNI:Java Native Interface縮寫,即Java本地接口
JNI是Java的編程框架繁扎,你可以抽象的把它理解為Java開發(fā)中的一種技巧幔荒,這種技巧可以使得Java與c/c++語言交互,即可以達(dá)到我們Java層調(diào)用c/c++的目的梳玫。
NDK:native?development?kit縮寫爹梁,中文解釋為基于原生程序接口的軟件開發(fā)工具
NDK的直接釋義不好理解,我們一樣從簡單易懂出來提澎,把Android NDK理解為Android中實現(xiàn)JNI的工具就可以了姚垃,這應(yīng)該很容易理解,雖然Android目前很大一部分是用Java開發(fā)出來的盼忌,但是對于一些Java中的工具Android進(jìn)行了修改或者封裝使得更符合Android本身的特性,比如Java中有一個類是URI积糯,android也有一個類似的類是Uri掂墓,二者功能類似,后者即是Android自己推薦使用的方式看成,所以Android NDK就是適應(yīng)Android特性來實現(xiàn)JNI的工具君编。
我們平時調(diào)用的底層功能都會打包成so文件供上層調(diào)用
二、實現(xiàn)一個最簡單的NDK項目(基于android studio3.0)
? ? ? ? ?前提:下載好NDK工具(SDKManager中下載)
? ? ? ? ?首先新建一個NDKDemo2的項目川慌,我們在包名目錄下建立一個調(diào)用c方法的類NDKHelper
? ? ? ? ? 在NDKHelper中我們聲明一個native方法
? ? ? ? ? ?native關(guān)鍵字表明該Java方法由非Java語言實現(xiàn)
? ? ? ? ? ? ?最后類的樣式如下:
? ? ? ? ? ? ? ?我們?yōu)闇?zhǔn)備生成的so文件起名為main吃嘿,NDKHelper會預(yù)加載該庫,平時使用so庫的小伙伴一定不會陌生了梦重,這是一個標(biāo)準(zhǔn)的使用方法兑燥,說明我們的native方法來自于main.so庫,其實做JNI也是最后生成so庫供以使用忍饰。
? ? ? ? ? ? ? ? 接下來有如下步驟:
? ? ? ? ? ? ? ?(1)生成提供該方法的c類
? ? ? ? ? ? ? ? 那么既然這樣贪嫂,我們就要用c語言實現(xiàn)一個同名方法用以調(diào)用,首先我們根據(jù)這個類生成一個c的頭文件(這是c語言的內(nèi)容如果不了解的話先照著做)艾蓝,在android studio下面terminal窗口執(zhí)行如下命令:
? ? ? ? ? ? ?這里我遇到了第一個坑,先把正確實現(xiàn)的步驟寫出來
?? ? ? ? ? ? 1斗塘、cd app/src/main/
? ? ? ? ? ? 2赢织、執(zhí)行javah -d jni -classpath ./Java 包名.NDKHelper
? ? ? ? ? ? ?這時在/src/main目錄下會生成一個jni目錄,如果接觸過JNI的coder應(yīng)該比較眼熟了馍盟,在目錄下會有一個名為zalex_person_com_ndkdemo2_NDKHelper的.h文件于置,也就是我們要生成的頭文件了。在這個實現(xiàn)中遇到過如下幾個坑:
? ? ? ? ? ? ? ?坑1(繞路):有些資料給出的步驟是去/build/intermediates/classes目錄下對.class文件執(zhí)行javah贞岭,其實在src目錄下執(zhí)行就可以八毯。
? ? ? ? ? ? ? 坑2(找不到類文件):有些資料給出的命令是?javah -d jni 包名.類型,在不同的本地環(huán)境下可能出現(xiàn)找不到類文件的提示瞄桨,網(wǎng)上有人給出的解決方法是在classpath中做配置话速,其實直接使用javah -d jni -classpath ?命令就可以了。
? ? ? ? ? ? ? 坑3(JNI目錄問題):注意JNI目錄的位置是在/src/main之下芯侥,很多同學(xué)的JNI目錄生成不對(比如使用了坑1方法生成再挪過來)泊交,位置錯誤導(dǎo)致最后運行時一直崩潰。
? ? ? ? ? ?生成完頭文件之后我們需要在寫一個.c文件并引用該頭文件(還是c的內(nèi)容柱查,不了解的同學(xué)直接copy)廓俭,在新生成的jni目錄下新建一個.c文件(New-->c/c++SourceFile---->后綴選擇.c),取名NDKHelper:
? ? ? ? ? ? 其中內(nèi)容如下:
? ? ? ? ? ? (2)將c文件通過NDK配置關(guān)聯(lián)起來
?? ? ? ? ? ?那現(xiàn)在我們對應(yīng)的c文件已經(jīng)有了(有些資料會讓你現(xiàn)在往jni目錄里添加mk文件唉工,暫且不提研乒,后面會說到),接下來就要做NDK和JNI配置淋硝,這時候你如果想要build工程已經(jīng)不行了雹熬,ide會提示一個錯誤:
? ? ? ? ? ? ? 這時最大的坑來了错维,出現(xiàn)了這個問題,我先把遇到的坑說一下橄唬,可以加深對NDK配置的理解赋焕,一開始在墻里墻外查到的解決方式都是這樣:
? ? ? ? ? ? ? ? 在gradle文件中添加該語句,錯誤就會解決了仰楚。很多資料接著讓你在gradle里面繼續(xù)ndk配置隆判,最后配置完的樣式如下:
? ? ? ? ? ? ? ?先解釋下ndk配置中的內(nèi)容,moduleName的注釋已近很清楚了僧界,我們生成的so庫會以該配置作為名稱侨嘀;abiFilters決定的是生成so庫的種類(在最后不同的設(shè)備會對應(yīng)生成一個不同的文件夾);jni.srcDirs =[]的作用是不讓項目自動進(jìn)行jni編譯捂襟,為什么這里要關(guān)閉jni編譯呢咬腕,我們翻譯下錯誤提示信息,Error:后面的意思是你的工程包含了c++文件但是整個系統(tǒng)并不支持native編譯葬荷。也就是說ide檢測到了有c++文件但是系統(tǒng)沒有配置native選項涨共,所以這里的做法是jni.srcDirs置空,ide檢測不到j(luò)ni文件就不會報錯了宠漩,通俗點說就是ide會去jni.srcDirs下去找jni文件举反,如果有的話就會檢測有沒有做native配置,沒有就會報錯扒吁。
? ? ? ? ? ? ? ?回到之前步驟火鼻,添加了兩個配置的地方,然后整個NDK的配置就完成了雕崩,只要在接著運行就可以了(或者有的資料配置的是jni.srcDirs = ['src/main/jni'])魁索。可是我們build工程并沒有在app/build/intermediates下發(fā)現(xiàn)生成了so文件(通常我們接觸到的都是so文件盼铁,NDK開發(fā)其實最后也是生成so打包進(jìn)apk)粗蔚,或者是我們發(fā)現(xiàn)生成了一個ndk文件夾但是里面只有幾個空的文件夾還有一個Android.mk文件,再運行項目發(fā)生崩潰捉貌,提示找不到native方法支鸡。
? ? ? ? ? ? ? 在這個地方我被困了很久,后來查了很多資料趁窃,發(fā)現(xiàn)這是android studio2.2之前NDK開發(fā)的方式牧挣,在android studio2.2版本引入了ndk-build和cmacke兩種方式進(jìn)行NDK,而在之前的提示信息里面也可以看到ide提示使用這兩種方式:
? ? ? ? ? ? ? (3)使用ndk-build繼續(xù)修改工程
? ? ? ? ? ? ? ?使用上更推薦cmake醒陆,但是對于初學(xué)瀑构,ndk-build方式更快捷(ndk工具中已經(jīng)集成ndk-build,cmake需要單獨下載),所以接著我修改成ndk-build方式實現(xiàn)寺晌,刪除目錄配置
? ? ? ? ? ? ? ? 并刪除ndk配置
? ? ? ? ? ? ? ? ?在工程上app目錄上右擊選擇link c++ project with Gradle世吨,這個選項的意思是導(dǎo)入外部的c++工程,也就是把我們寫好的c當(dāng)做外部工程導(dǎo)入:
? ? ? ? ? ? ? 在下拉框中選擇ndk-build呻征,并選擇對應(yīng)的Android.mk文件的位置:
? ? ? ? ? ? ? ?這里說明一下mk文件的作用耘婚,mk文件其實里面也就是一些ndk的文件路徑配置、生成so庫名字陆赋、生成平臺等的配置沐祷,之前我們做的moduleName、abiFilters的配置可以直接寫在mk文件里:
? ? ? ? ? ? ? ? ?我們需要在jni目錄里加入如上兩個mk文件Android.mk和Application.mk攒岛,如圖是最基本的配置赖临,解釋下每個配置的注意的地方:
Android.mk: ? ? ?LOCAL_PATH和兩個include照寫就行了,這三個配置google出來的意思比較生澀灾锯,墻內(nèi)的解釋也比較難懂兢榨,我們要關(guān)注的事LOCAL_MUDLE 和 LOCAL_SRC_FILES,LOCAL_MUDLE其實就是我們之前配置的moduleName顺饮,指定了生成so庫的名字吵聪,LOCAL_SRC_FILES則是我們引用的c文件位置。
Application.mk:APP_ABI就是abiFilters了领突,所以之前我們做的配置都可以在這里寫暖璧,賦值為all表明全平臺生成,如果有多個用空格分開,APP_ABI := armeabi-v7a?armeabi君旦。APP_STL指運行庫類型,通常都是stlport_static嘲碱,表示以靜態(tài)鏈接方式使用的sttport版本的STL(寫出這個配置翻譯金砍,是挺生澀的吧)。
? ? ? ? ? ? ? ? ?項目build完之后麦锯,回想一下恕稠,我們選擇的mk文件是Android.mk,Applicaiton.mk還沒配置進(jìn)去扶欣,打開app/build.gradle鹅巍,你會發(fā)現(xiàn)多了一個配置:
? ? ? ? ? ? ? ? 所以Android.mk最后還是在gradle里面引入,其實我們就可以直接在build.gradle里加入這句引入代碼料祠,剛才用的方式是可視化界面的操作方式骆捧。接著我們在里面配置Application.mk:
最后再build一下,終于大功告成了髓绽!
如果生成了如上兩個文件件敛苇,并且ndkBuild文件夾下有對應(yīng)so文件則說明成功了。
但是其實還有最后一個坑
我發(fā)現(xiàn)Application.mk中APP_ABI的配置并不起作用顺呕,于是在build.gradle中做了最后一步修改:
ndkBuild下生成的so文件也變了枫攀,終于ok了括饶!
NDKHelper的getNDKPrint方法已經(jīng)可以調(diào)用了,我們在c里面讓它返回一個This is Jni test!!!的字符串来涨,趕快試試吧图焰!
(三)總結(jié)
回想一下ndk的開發(fā)方式其實是這樣:
工程--------------->mk文件(包含c/c++文件路徑、平臺配置等)-------------------->c/c++文件
關(guān)鍵就是正確的引入mk文件蹦掐,以及mk文件的配置正確
有一些bug比如APP_ABI不生效或許是采用ndk-buil方式的緣故技羔,看懂的小伙伴趕快去試試cmake吧!