JNI是Java Native Interface的縮寫,JNI是一種機制,有了它就可以在java程式中調用其他native代碼,或者使native代碼調用java層的代碼。也就是說,有了JNI我們可以使Android項目中,java層與native層各自發揮所長並相互配合。如所示,JNI在Android中所處的位置。
好吧誰不知道JNI應該在JAVA和Native的中間呢?
JNI相對與native層來說是一個介面,java層的程式想訪問native層,必須通過JNI,反過來也一樣。下面我們來看幾個問題。
1,如何告訴VM(虛擬機器)java層需要調用native層的哪些libs?
我們知道java程式是運行在VM上的,而Native層的libs則不然。所以為了讓java層能訪問native層的libs,必須得告訴VM要使用哪些native層的libs。下面看一段代碼
[java]
view plaincopy
- public class MediaPlayer
- {
- ...
-
- static {
- System.loadLibrary("media_jni");
- native_init();
- }
-
- ...
-
- private native final void native_setup(Object mediaplayer_this);
-
- ...
- }
可以看到上面的代碼中,在MediaPlayer類中有一段static塊包圍起來的代碼,其中System.loadLibrary("media_jni")就是告訴VM去載入libmedia_jni.so這個動態庫,那麼這個動態庫什麼時候被載入呢?因為static語句塊的原因,所以在MediaPlayer第一次執行個體化的時候就會被載入了。這段代碼中,我們還看到了一個函數native_init(),該函數被申明為native型,就是告訴VM該函數由native層來實現。
2,如何做到java層到native層的映射。
事實上我想表達的意思是,如何完成java層的代碼到native層代碼的映射,例如上面的代碼中有一個native函數native_init(),那麼如何使這個函數映射到一個由C/C++(或者其他語言)實現的具體函數呢?PS:本菜鳥,表達能力欠缺,不知道大家有沒有看明白。
當VM執行到System.loadLibrary()的時候就會去執行native libs中的JNI_OnLoad(JavaVM* vm, void* reserved)函數,因為JNI_OnLoad函數是從java層進入native層第一個調用的方法,所以可以在JNI_OnLoad函數中完成一些native層組件的初始化工作,同時更加重要的是,通常在JNI_jint
JNI_OnLoad(JavaVM* vm, void* reserved)函數中會註冊java層的native方法。下面看一段代碼:
[java]
view plaincopy
- jint JNI_OnLoad(JavaVM* vm, void* reserved)
- {
- JNIEnv* env = NULL;
- jint result = -1;
- //判斷一下JNI的版本
- if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
- LOGE("ERROR: GetEnv failed\n");
- goto bail;
- }
- assert(env != NULL);
-
- if (register_android_media_MediaPlayer(env) < 0) {
- LOGE("ERROR: MediaPlayer native registration failed\n");
- goto bail;
- }
-
- if (register_android_media_MediaRecorder(env) < 0) {
- LOGE("ERROR: MediaRecorder native registration failed\n");
- goto bail;
- }
-
- if (register<span style="font-size:16px;">_android_media_MediaScanner(env) < 0) {
- LOGE("ERROR: MediaScanner native registration failed\n");
- goto bail;
- }</span>
-
- if (register_android_media_MediaMetadataRetriever(env) < 0) {
- LOGE("ERROR: MediaMetadataRetriever native registration failed\n");
- goto bail;
- }
-
- if (register_android_media_AmrInputStream(env) < 0) {
- LOGE("ERROR: AmrInputStream native registration failed\n");
- goto bail;
- }
-
- if (register_android_media_ResampleInputStream(env) < 0) {
- LOGE("ERROR: ResampleInputStream native registration failed\n");
- goto bail;
- }
-
- if (register_android_media_MediaProfiles(env) < 0) {
- LOGE("ERROR: MediaProfiles native registration failed");
- goto bail;
- }
-
- /* success -- return valid version number */
- result = JNI_VERSION_1_4;
-
- bail:
- return result;
- }
上面這段代碼的JNI_OnLoad(JavaVM* vm, void* reserved)函數實現與libmedia_jni.so庫中。上面的代碼中調用了一些形如register_android_media_MediaPlayer(env)的函數,這些函數的作用是註冊native method。我們來看看函數register_android_media_MediaPlayer(env)的實現。
[java]
view plaincopy
- // This function only registers the native methods
- static int register_android_media_MediaPlayer(JNIEnv *env)
- {
- return AndroidRuntime::registerNativeMethods(env,
- "android/media/MediaPlayer", gMethods, NELEM(gMethods));
[java]
view plaincopy
- /*
- * Register native methods using JNI.
- */
- /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
- const char* className, const JNINativeMethod* gMethods, int numMethods)
- {
- return jniRegisterNativeMethods(env, className, gMethods, numMethods);
- }
最終jniRegisterNativeMethods函數完成java標準的native函數的映射工作。下面我們來具體的看看上面這個函數中各個參數的意義。
a,JNIEnv* env,關於JNIEnv我在google上找到了這些資訊:
JNI defines two key data structures, "JavaVM" and "JNIEnv". Both of these are essentiallypointers to pointers to function tables. (In the C++ version, they're classes with apointer to a function table and a member function for each JNI function that indirects
throughthe table.) The JavaVM provides the "invocation interface" functions,which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process,but Android only allows one.
The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv asthe first argument.
The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads.If a piece of code has no other way to get its JNIEnv, you should sharethe JavaVM, and useGetEnv
to discover the thread's
JNIEnv. (Assuming it has one; see AttachCurrentThread
below.)
這裡需要注意一點的是,JNIEnv是一個線程的局部變數,這以為這JNIEnv是存在與多線程環境下的,因為 VM 通常是多執行緒(Multi-threading)的執行環境。每一個執行緒在呼叫JNI_OnLoad()時,所傳遞進來的 JNIEnv 指標值都是不同的。為了配合這種多執行緒的環境,C/C++組件開發人員在撰寫本地函數時,可藉由 JNIEnv 指標值之不同而避免執行緒的資料衝突問題,才能確保所寫的本地函數能安全地在 Android 的多執行緒 VM 裡安全地執行。基於這個理由,當在
呼叫 C/C++ 組件的函數時,都會將 JNIEnv 指標值傳遞給它。
b,char* className,這個沒什麼好說的,java空間中類名,其中包含了包名。
c,JNINativeMethod* gMethods,傳遞進去的是一個JNINativeMethod類型的指標gMethods,gMethods指向一個JNINativeMethod數組,我們先看看JNINativeMethod這個結構體。
[java]
view plaincopy
- typedef struct {
- const char* name; /*Java 中函數的名字*/
- const char* signature; /*描述了函數的參數和傳回值*/
- void* fnPtr; /*函數指標,指向 C 函數*/
- } JNINativeMethod;
再來看看gMethods數組
[java]
view plaincopy
- static JNINativeMethod gMethods[] = {
- {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSource},
- 。。。
- {"setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer_setAuxEffectSendLevel},
- {"attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer_attachAuxEffect},
- {"getOrganDBIndex", "(II)I", (void *)android_media_MediaPlayer_getOrganDBIndex},
- };
d,int numMethods,不解釋。
這樣一來就完成了java native函數到到JNI層函數的映射。當然具體功能實現還是由JNI層函數來調用C/C++相應的功能函數
-------------------------------------------至此只講解了JNI大致流程,下次會具體講解一下JNI的細節,本菜鳥能力有限,blog中若有錯誤的地方還請給為指正,謝謝。