Android Studio NDK JNI 編程最小白最簡單入門Demo

Android Studio 編寫JNI有兩種方式

  1. 通過ndk-build編寫,和eclipse類似,需要配置Android.mk、Application.mk文件。之前的一些開源庫還是使用此種方式編寫.so,因此還是需要了解此種方式。
  2. 通過cmake 編寫,Android Studio 2.2(含)之后引入更方便的cmake,需要配置CMakeLists.txt。
    下面我們就通過實例一步步了解這兩種方式異同點。

ndk-build

首先下載ndk,可以在單獨下載ndk包,解壓到本地目錄,再將工程里配置ndk路徑至解壓的目錄,或者直接在Android Studio里下載,下載解壓成功后,自動配置路徑,無需手動配置。Android Studio里下載方式如下:


下載ndk

配置路徑:


配置ndk路徑

ndk準備好后,接下來開始專注于工程本身。
首先創(chuàng)建一個Android Library Module
創(chuàng)建一個加法器功能的類:CalNum

public class CalNum {
    //暴露給外界的接口
    public float testAdd(float a, float b) {
        return addFloat(a, b);
    }

    //通過jni,調(diào)用c/c++ 函數(shù)
    private static native float addFloat(float a, float b);
}

上面的addFloat是本地方法,該怎么實現(xiàn)呢?我們知道C語言需要一個頭文件(.h)聲明函數(shù)原型,需要一個源文件(.c)實現(xiàn)函數(shù)功能,因此需要創(chuàng)建兩個文件。

  1. 創(chuàng)建頭文件
    先了解下我們的工程目錄結(jié)構(gòu)
TestJni/testnum/src/main/java

TestJni是工程名,testnum是Module名,java目錄下存放的是純java代碼。
頭文件需要聲明addFloat函數(shù),jni函數(shù)名比較特殊,通過javah -jni命令生成。
進入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文件所在目錄是臨時的,我們一般會將c/c++文件放入一個特定目錄:jni,因此我們需要創(chuàng)建jni目錄,右鍵點擊Module:


    創(chuàng)建jni目錄

    將之前的.h文件拷貝到j(luò)ni目錄下(生成的目錄名為"jni",Android Studio 展示時為"cpp",下面提到的jni等同cpp)。有了.h文件,現(xiàn)在我們來編寫.c文件,在jni目錄下創(chuàng)建.c文件,右鍵點擊jni:
    創(chuàng)建.c文件

    實現(xiàn).c文件函數(shù)功能:
    .c文件功能

    該函數(shù)功能實際就是計算兩個數(shù)加結(jié)果。
    好了,現(xiàn)在已經(jīng)文成.h和.c文件的編寫,那么如何將c文件編寫為.so文件呢?這個時候就需要借助Android.mk和Application.mk文件了,這兩個文件通過特定語法配置一些參數(shù),這些文件將決定如何生成一個makefile文件,編譯器就會依據(jù)makefile文件編譯c源文件,最終生成.so文件。
    如何編寫Android.mk文件呢?在jni目錄下新建Android.mk文件:


    Android.mk
  • LOCAL_PATH 指的是當前目錄
  • include $(CLEAR_VARS) 指的是清空變量
  • LOCAL_MODULE := calnum 指的是生成.so文件的名稱,全稱:libcalnum.so
  • LOCAL_SRC_FILES 指的是待編譯的源文件
  • include $(BUILD_SHARED_LIBRARY) 指的是生成的庫類型,這里是動態(tài)庫
    Android.mk 還有其它語法參數(shù),這里就不展開說明了。
    如何編寫Application.mk文件呢?在jni目錄下新建Application.mk文件。


    Application.mk
  • APP_ABI 指的是生成哪些cpu架構(gòu)支持的.so文件,all表示所有支持的架構(gòu),如果只需要生成某一種或幾種平臺支持的.so,填相應(yīng)的名字即可,比如x86、armeabi-v7a等。

ps:經(jīng)測試,這里無論怎么填,都默認生成所有支持平臺的.so。

至此,jni目錄下的文件已經(jīng)準備齊全:


jni目錄

這時候我們開始make module,然而令人失望的是卻是報錯,原因是我們僅僅準備了jni相關(guān)文件,編譯器并不知道如何去操作jni文件,而我們又知道Android.mk記錄這編譯相關(guān)東西,因此應(yīng)當先讓編譯器找到Android.mk文件。
在module的build.gradle里,android層級內(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/ 目錄下,因此需要通過上級目錄索引到Android.mk

這個時候我們再make module,成功了!那么生成的.so文件在哪呢?首先定位到app build/intermediates 目錄下,搜索".so"文件,經(jīng)過篩選,找到如下目錄:

build/intermediates/ndkBuild/debug/obj/local

該目錄下文件為:


生成.so

每個目錄下有對應(yīng)平臺的.so庫,如下:


.so

我們應(yīng)該注意到了,這里只是生成了4種平臺下的.so庫,我們明明記得一般是支持7種平臺呢?沒錯,這里確實少了 armeabi,mips,mips64平臺,因為在ndk17開始不再支持這三種平臺,而我們這里使用的ndk版本是20。那么如果想所有平臺都支持呢?那么使用的ndk版本需要低于17(經(jīng)過測試,ndk16也不行,最好是15及其以下)。

要查看ndk支持的abi,定位到ndk目錄下,執(zhí)行ndk-which命令,即可輸出該版本ndk支持的abi。

上面我們說了支持的平臺不夠多,但是我們還想減少支持的平臺數(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"
    }

想要生成哪個平臺,填其名字即可。
現(xiàn)在.so文件已經(jīng)生成,另一個模塊如何調(diào)用呢?記得我們創(chuàng)建模塊的時候是創(chuàng)建了Android Library,也就是說我們module編譯成了.jar文件,該.jar文件負責(zé)調(diào)用.so文件里的函數(shù),并且.jar文件暴露給外界模塊接口,外界模塊間接調(diào)用了.so,整個流程下來就完成了一個最簡單的jni編程、實例調(diào)用。那具體怎么配置module調(diào)用呢?有兩種方法:

1、調(diào)用者工程內(nèi)直接依賴Android Library module,優(yōu)點是方便調(diào)試,前提是我們有Android Library module源碼
2、 調(diào)用者工程內(nèi)依賴.jar包,現(xiàn)成的第三方庫一般以jar包形式提供。

下面分別簡要說明兩者的配置方式

  • 直接依賴module:


    選擇依賴

    選中Module dependency,選擇需要依賴的module,確定后,再到調(diào)用者的module build.gradle里查看:


    依賴module

    最后一項就是之前界面操作的結(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包,依然可以通過界面操作依賴:


    選擇依賴

    選中jar dependency,選擇需要依賴的jar,確定后,再到調(diào)用者的module build.gradle里查看:


    依賴jar

    最后一項就是之前界面操作的結(jié)果。
    jar包依賴已經(jīng)搞定,還有so庫呢?也是有兩種方式,對應(yīng)上面和兩種依賴jar包方式:
  • 直接依賴module
    這種方式下不用配置so庫位置
  • 間接依賴module
    定位到調(diào)用者module src/main 目錄下,新建文件夾,名為:"jniLibs“,然后將之前生成的各個平臺的so庫放入該文件夾下:


    jniLibs

    當然,如果不想放在該目錄下,也可以和jar包一起放在"libs"文件夾下,前提是需要在build.gradle android 層級下指明so庫的位置

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

至此,jar包和so庫都準備好了,調(diào)用者就可以調(diào)用暴露出來的接口進行訪問了。

        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

通過上面ndk-build方式可知,需要我們配置Android.mk Application.mk文件,比較繁瑣。Google為此推出了新的編譯方式-cmake,那么cmake需要怎么做呢?
首先下載cmake工具


cmake工具

其次,新建project的時候,會有一個c++支持選項,勾選即可。project創(chuàng)建完畢后,會發(fā)現(xiàn)比沒勾選時多了幾個文件:
1、src/main 目錄下新建了jni文件夾,并且預(yù)先放置了一個cpp文件:


native-lib.cpp

和ndk-build jni目錄一致的,當然也可以放.c 和 .h文件。
2、app 目錄下多了個CMakeLists.txt,該文件的作用和ndk-build時使用的.mk文件類似。
cmakelists.txt

我們來看看該文件里邊的語法:


cmakelists.txt 內(nèi)容
  • add_library
    native-lib 指的是要生成的庫名稱
    SHARED 指的是生成的庫為動態(tài)庫
    src/main/cpp/native-lib.cpp 指的是需要編譯的源文件
  • find_library 配置的是需要依賴的外部庫
  • target_link_libraries 配置的是最終將多個庫鏈接起來
    3、build.gradle 新增了幾項配置:


    build.gradle

    配置 cmake 編譯參數(shù)等。

        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }

讓編譯器知道CMakeLists.txt 位置

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }

注:第三點是手動新增的,新建的project并沒有,編譯會報錯,加上第三點解決編譯報錯問題。

至此,cmake方式編譯jni配置工作就完成了,只需要簡單的勾選就可以支持ndk編程,是不是覺得比之前方便多了。也許你會問,創(chuàng)建工程時忘了添加c++支持,后面有需要編譯jni怎么辦呢?還是按照上面的方法,手動添加:

1、下載cmake工具
2、新建jni,編寫.c/.h 、c++源文件
3、新建CMakeLists.txt,配置其中參數(shù)
4、配置build.gradle

ndk-build方式和cmake方式編寫簡單入門jni程序已經(jīng)梳理完畢,總結(jié)幾個比較關(guān)鍵的點:

1、兩種方式需要哪些配置文件容易搞混亂(相對來說,具體配置文件語法都可以查得到,反而比較簡單)
2、配置時路徑容易搞糊涂,實際上只要厘清當前配置文件所在的位置、待指向的配置的文件所在的位置,相對位置就整明白了,進而填上相對索引即可訪問

最后,整個效果圖:


效果

本文基于Android Studio 3.2 NDK 20

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容