Android架構(gòu)師之路-JNI與NDK編程知識(shí)基礎(chǔ)詳解(c++音視頻編碼基礎(chǔ))
前沿小記
- 1、Android架構(gòu)師要學(xué)習(xí)的知識(shí)點(diǎn)有很多,后面我會(huì)總結(jié)下關(guān)于Android開發(fā)中jni和ndk開發(fā)的知識(shí)點(diǎn),當(dāng)前基礎(chǔ)是越牢固越好,后期學(xué)習(xí)起來就不會(huì)太累,一點(diǎn)就懂;
- 2、今天我們就來總結(jié)下jni和ndk的基礎(chǔ)知識(shí)點(diǎn);
- 3、Android 平臺(tái)從一開就已經(jīng)支持了C/C++了。我們知道Android的SDK主要是基于Java的,所以導(dǎo)致了在用Android SDK進(jìn)行開發(fā)的工程師們都必須使用Java語言。不過,Google從一開始就說明Android也支持JNI編程方式,也就是第三方應(yīng)用完成可以通過JNI調(diào)用自己的C動(dòng)態(tài)度;
一、什么是ndk
- NDK提供了一系列的工具,幫助開發(fā)者快速開發(fā)C(或C++)的動(dòng)態(tài)庫,并能自動(dòng)將so和java應(yīng)用一起打包成apk。這些工具對(duì)開發(fā)者的幫助是巨大的;
- NDK集成了交叉編譯器,并提供了相應(yīng)的mk文件隔離CPU、平臺(tái)、ABI等差異,開發(fā)人員只需要簡(jiǎn)單修改mk文件(指出“哪些文件需要編譯”、“編譯特性要求”等),就可以創(chuàng)建出so;
- NDK可以自動(dòng)地將so和Java應(yīng)用一起打包,極大地減輕了開發(fā)人員的打包工作;
- NDK提供了一份穩(wěn)定、功能有限的API頭文件聲明;Google明確聲明該API是穩(wěn)定的,在后續(xù)所有版本中都穩(wěn)定支持當(dāng)前發(fā)布的API。從該版本的NDK中看出,這些API支持的功能非常有限,包含有:C標(biāo)準(zhǔn)庫(libc)、標(biāo)準(zhǔn)數(shù)學(xué)庫(libm)、壓縮庫(libz)、Log庫(liblog);
- NDK的發(fā)布,使“Java+C”的開發(fā)方式終于轉(zhuǎn)正,成為官方支持的開發(fā)方式;
- 使用NDK,我們可以將要求高性能的應(yīng)用邏輯使用C開發(fā),從而提高應(yīng)用程序的執(zhí)行效率;
- 使用NDK,我們可以將需要保密的應(yīng)用邏輯使用C開發(fā)。畢竟,Java包都是可以反編譯的;
- NDK促使專業(yè)so組件商的出現(xiàn):比如視頻庫的編譯、音頻庫、圖片庫濾鏡等等;
- NDK將使Android平臺(tái)支持C開發(fā)的開端;NDK提供了的開發(fā)工具集合,使開發(fā)人員可以便捷地開發(fā)、發(fā)布C組件。同時(shí),Google承諾在NDK后續(xù)版本中提高“可調(diào)式”能力,即提供遠(yuǎn)程的gdb工具,使我們可以便捷地調(diào)試C源碼;
二、為什么使用NDK
在平臺(tái)之間移植其應(yīng)用;
重復(fù)使用現(xiàn)在庫,或者提供其自己的庫重復(fù)使用;
在某些情況下提性能,特別是像游戲這種計(jì)算密集型應(yīng)用;
使用第三方庫,現(xiàn)在許多第三方庫都是由C/C++庫編寫的,比如Ffmpeg這樣庫;
不依賴于Dalvik Java虛擬機(jī)的設(shè)計(jì);
代碼的保護(hù)。由于APK的Java層代碼很容易被反編譯,而C/C++庫反編譯難度大;
三、jni詳解
1、jni是什么?以及和ndk關(guān)系
- JNI,全稱為Java Native Interface,即Java本地接口,JNI是Java調(diào)用Native 語言的一種特性。通過JNI可以使得Java與C/C++機(jī)型交互。即可以在Java代碼中調(diào)用C/C++等語言的代碼或者在C/C++代碼中調(diào)用Java代碼。由于JNI是JVM規(guī)范的一部分,因此可以將我們寫的JNI的程序在任何實(shí)現(xiàn)了JNI規(guī)范的Java虛擬機(jī)中運(yùn)行;
- 在Android Framework中,需要提供一種媒介或 橋梁,將Java層(上層)與C/C++層(下層)有機(jī)的聯(lián)系起來,使得他們互相協(xié)調(diào)完成某些任務(wù)。而充當(dāng)這種媒介的就是Java本地接口(JNI,Java Native Interface);
- JNI提供一些列的接口,允許Java類與C/C++等本地編輯語言(在JNI中,這些語言被稱為 本地語言)編寫的應(yīng)用 程序、模塊 、庫進(jìn)行交互操作。比如,在Java類中使用C語言庫中的函數(shù)或在C語言中使用 Java類庫,都需要借助JNI;
- Android NDK是一個(gè)開發(fā)工具集,提供一系列工具快速開發(fā)C/C++的動(dòng)態(tài)庫,并能自動(dòng)將 .so/.dll 和 Java 應(yīng)用一起打包到Apk;NDK提供工具可以方便JNI調(diào)用C/C++,而且提供了交叉編譯器可以修改.mk文件生成特定CPU平臺(tái)的動(dòng)態(tài)庫,并能將so和java應(yīng)用一起打包到apk中;簡(jiǎn)單說就是JNI負(fù)責(zé)Java與C/C++進(jìn)行互相操作,NDK提供工具方便在Android平臺(tái)使用JNI;
2、JNI開發(fā)流程的步驟

- 在Java中先聲明一個(gè)native方法;
- 編譯Java源文件javac得到.class文件;
- 通過javah -jni命令導(dǎo)出JNI的.h頭文件;
- 使用Java需要交互的本地代碼,實(shí)現(xiàn)在Java中聲明的Native方法;
- 將本地代碼編譯成動(dòng)態(tài)庫(Windows系統(tǒng)下是.dll文件,如果是Linux系統(tǒng)下是.so文件,如果是Mac系統(tǒng)下是.jnilib);
- 通過Java命令執(zhí)行Java程序,最終實(shí)現(xiàn)Java調(diào)用本地代碼;

3、 JNI數(shù)據(jù)結(jié)構(gòu)

JNI函數(shù)表的組成就像C++的虛函數(shù)表。虛擬機(jī)可以運(yùn)行多張函數(shù)表,舉例來說,一張調(diào)試函數(shù)表,另一張是調(diào)用函數(shù)表。JNI接口指針僅在當(dāng)前線程中起作用。這意味著指針不能從一個(gè)線程進(jìn)入另一個(gè)線程,然而,可以在不同的咸亨中調(diào)用本地方法;
- jdouble test (JNIEnv *env, jobject obj, jint i, jstring s)
- {
- const char *str = (*env)->GetStringUTFChars(env, s, 0);
- (*env)->ReleaseStringUTFChars(env, s, str);
- return 10;
- }

JNI有自己的原始數(shù)據(jù)類型和數(shù)據(jù)引用類型如下

四、jni交互原理詳解
1、JavaVM
JavaVM是Java虛擬機(jī)在JNI層的代表,JNI全局僅僅有一個(gè)JavaVM結(jié)構(gòu)中封裝了一些函數(shù)指針(或叫函數(shù)表結(jié)構(gòu)),JavaVM中封裝的這些函數(shù)指針主要是對(duì)JVM操作接口。另外,在C和C++中的JavaVM的定義有所不同,在C中JavaVM是JNIInvokeInterface_類型指針,而在C++中有對(duì)JNIInvokeInterface_進(jìn)行了一次封裝,比C中少了一個(gè)參數(shù),這也是為什么JNI代碼更推薦使用C++來編寫的原因;
2、JNIEnv
JNIEnv是一個(gè)線程相關(guān)的結(jié)構(gòu)體,該結(jié)構(gòu)體代表了Java在本線程的執(zhí)行環(huán)境
JNIEnv是當(dāng)前Java線程的執(zhí)行環(huán)境,一個(gè)JVM對(duì)應(yīng)一個(gè)JavaVM結(jié)構(gòu),而一個(gè)JVM中可能創(chuàng)建多個(gè)Java線程,每個(gè)線程對(duì)應(yīng)一個(gè)JNIEnv結(jié)構(gòu),它們保存在線程本地存儲(chǔ)TLS中。因此,不同的線程的JNIEnv是不同,也不能相互共享使用。JNIEnv結(jié)構(gòu)也是一個(gè)函數(shù)表,在本地代碼中通過JNIEnv的函數(shù)表來操作Java數(shù)據(jù)或者調(diào)用Java方法。也就是說,只要在本地代碼中拿到了JNIEnv結(jié)構(gòu),就可以在本地代碼中調(diào)用Java代碼;
調(diào)用Java 函數(shù):JNIEnv代表了Java執(zhí)行環(huán)境,能夠使用JNIEnv調(diào)用Java中的代碼;
操作Java代碼:Java對(duì)象傳入JNI層就是jobject對(duì)象,需要使用JNIEnv來操作這個(gè)Java對(duì)象;
3、JNIEnv和JavaVM的區(qū)別
- JavaVM:JavaVM是Java虛擬機(jī)在JNI層的代表,JNI全局僅僅有一個(gè);
- JNIEnv:JavaVM 在線程中的代碼,每個(gè)線程都有一個(gè),JNI可能有非常多個(gè)JNIEnv;
4、JNIEnv與線程
- JNIEnv是線程相關(guān)的,即在每一個(gè)線程中都有一個(gè)JNIEnv指針,每個(gè)JNIEnv都是線程專有的,其他線程不能使用本線程中的JNIEnv,即線程A不能調(diào)用線程B的JNIEnv,所以JNIEnv不能跨線程;
- JNIEnv只在當(dāng)前線程有效:JNIEnv僅僅在當(dāng)前線程有效;
- JNIEnv不能在線程之間進(jìn)行傳遞,在同一個(gè)線程中,多次調(diào)用JNI層方便,傳入的JNIEnv是同樣的;
- 本地方法匹配多個(gè)JNIEnv:在Java層定義的本地方法,能夠在不同的線程調(diào)用,因此能夠接受不同的JNIEnv;
5、JNIEnv結(jié)構(gòu)

6、JNIEnv相關(guān)的常用函數(shù)
- 創(chuàng)建Java中的對(duì)象
- jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);
- jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list);
- jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*);
- 字符串相關(guān)
- jstring (*NewString)(JNIEnv*, const jchar*, jsize);
- jsize (*GetStringLength)(JNIEnv*, jstring);
- const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
- void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
- jstring (*NewStringUTF)(JNIEnv*, const char*);
- jsize (*GetStringUTFLength)(JNIEnv*, jstring);
- /* JNI spec says this returns const jbyte*, but that's inconsistent */
- const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
- 獲取數(shù)組相關(guān)
- jsize GetArrayLength(jarray array)
- { return functions->GetArrayLength(this, array); }
- jobject GetObjectArrayElement(JNIEnv *env,
- jobjectArray array, jsize index);
總結(jié):
- 以上是學(xué)習(xí)jni的基礎(chǔ)知識(shí)點(diǎn),必須理解的,不懂的就問就學(xué);
- jni還有很多知識(shí)點(diǎn),比如動(dòng)態(tài)注冊(cè),靜態(tài)注冊(cè)等等;
- 要學(xué)習(xí)一點(diǎn)c語言和c++的基礎(chǔ)知識(shí)點(diǎn),后面會(huì)講到總結(jié)。
本文轉(zhuǎn)載自微信公眾號(hào)「Android開發(fā)編程」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系A(chǔ)ndroid開發(fā)編程公眾號(hào)。