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

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

3、 JNI數據結構

JNI函數表的組成就像C++的虛函數表。虛擬機可以運行多張函數表,舉例來說,一張調試函數表,另一張是調用函數表。JNI接口指針僅在當前線程中起作用。這意味著指針不能從一個線程進入另一個線程,然而,可以在不同的咸亨中調用本地方法;
- 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有自己的原始數據類型和數據引用類型如下

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

6、JNIEnv相關的常用函數
- 創建Java中的對象
- jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);
- jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list);
- jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*);
- 字符串相關
- 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*);
- 獲取數組相關
- jsize GetArrayLength(jarray array)
- { return functions->GetArrayLength(this, array); }
- jobject GetObjectArrayElement(JNIEnv *env,
- jobjectArray array, jsize index);
總結:
- 以上是學習jni的基礎知識點,必須理解的,不懂的就問就學;
- jni還有很多知識點,比如動態注冊,靜態注冊等等;
- 要學習一點c語言和c++的基礎知識點,后面會講到總結。
本文轉載自微信公眾號「Android開發編程」,可以通過以下二維碼關注。轉載本文請聯系Android開發編程公眾號。