Android Studio NDK JNI 編程最小白最簡(jiǎn)單入門Demo

Android Studio 編寫JNI有兩種方式

  1. 通過(guò)ndk-build編寫奔浅,和eclipse類似馆纳,需要配置Android.mk、Application.mk文件乘凸。之前的一些開(kāi)源庫(kù)還是使用此種方式編寫.so厕诡,因此還是需要了解此種方式。
  2. 通過(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

配置路徑:


配置ndk路徑

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è)文件燎窘。

  1. 創(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)備齊全:


jni目錄

這時(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

該目錄下文件為:


生成.so

每個(gè)目錄下有對(duì)應(yīng)平臺(tái)的.so庫(kù)柴灯,如下:


.so

我們應(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包種的類


jar包里的類

cmake

通過(guò)上面ndk-build方式可知礁哄,需要我們配置Android.mk Application.mk文件,比較繁瑣溪北。Google為此推出了新的編譯方式-cmake夺脾,那么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文件:


native-lib.cpp

和ndk-build jni目錄一致的婉弹,當(dāng)然也可以放.c 和 .h文件。
2终吼、app 目錄下多了個(gè)CMakeLists.txt镀赌,該文件的作用和ndk-build時(shí)使用的.mk文件類似。
cmakelists.txt

我們來(lái)看看該文件里邊的語(yǔ)法:


cmakelists.txt 內(nèi)容
  • 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驮配,一起剝皮案震驚了整個(gè)濱河市侈净,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌僧凤,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件元扔,死亡現(xiàn)場(chǎng)離奇詭異躯保,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)澎语,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門途事,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人擅羞,你說(shuō)我怎么就攤上這事尸变。” “怎么了减俏?”我有些...
    開(kāi)封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵召烂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我娃承,道長(zhǎng)奏夫,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任历筝,我火速辦了婚禮酗昼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘梳猪。我一直安慰自己麻削,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布春弥。 她就那樣靜靜地躺著呛哟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惕稻。 梳的紋絲不亂的頭發(fā)上竖共,一...
    開(kāi)封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音俺祠,去河邊找鬼公给。 笑死借帘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的淌铐。 我是一名探鬼主播肺然,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腿准!你這毒婦竟也來(lái)了际起?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吐葱,失蹤者是張志新(化名)和其女友劉穎街望,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弟跑,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡灾前,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了孟辑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哎甲。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖饲嗽,靈堂內(nèi)的尸體忽然破棺而出炭玫,到底是詐尸還是另有隱情,我是刑警寧澤貌虾,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布吞加,位于F島的核電站,受9級(jí)特大地震影響尽狠,放射性物質(zhì)發(fā)生泄漏榴鼎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一晚唇、第九天 我趴在偏房一處隱蔽的房頂上張望巫财。 院中可真熱鬧,春花似錦哩陕、人聲如沸平项。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)闽瓢。三九已至,卻和暖如春心赶,著一層夾襖步出監(jiān)牢的瞬間扣讼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工缨叫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留椭符,地道東北人荔燎。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像销钝,于是被迫代替她去往敵國(guó)和親有咨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容