一個簡單的樣例讓我們初步地了解JNI的作用,可是關於JNI中的一些概念還是須要了解清楚,才可以更好的去利用它來實現我們想要做的事情。
那么C++和Java之間的是怎樣通過JNI來進行互相調用的呢?
我們知道。在Android中,當Java文件被編譯成dex文件之后,會由類載入器載入到Dalvik VM(DVM)中,由DVM來進行解釋,翻譯成機器語言之后,才干由機器來執行。
而對於C/C++來說,其源碼經由Android提供的NDK工具包,能夠編譯成可運行動態庫(即.so文件)。之后。Java和C++之間就能夠進行通訊了。
那么,在這里,能夠想像,Java的Dex字節碼和C/C++的so庫肯定是同一時候執行在一個DVM之中。它們是共同使用一個進程空間的,否則,它們怎么彼此溝通呢?
所以在這里,一個關鍵的中間區域就是Dalvik VM。而對於C/C++,當它們也被載入進DVM之后,由C/C++實現的函數方法等都會被載入在DVM中的函數表中。
假設想要在C/C++中調用函數,它們必需要有個東西可以讓其訪問到這個虛擬機中的函數表。
而這個東西就是JNIEnv *。
當我們利用javah生成的C/C++的頭文件的時候。例如以下:
JNIEXPORT jstring JNICALL Java_com_lms_jni_HwDemo_printHello (JNIEnv *e, jobject j) { return (**e).NewStringUTF(e,"Hello from T" ); }
我們能夠看到這種方法有兩個參數。當中第一個就是JNIEnv *。而我們在Java端定義這種方法的時候。是沒有參數的,例如以下:
public native String printHello();
struct _JNIEnv; struct _JavaVM; typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) typedef _JNIEnv JNIEnv; //C++中JNIEnv的類型 typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv; //C中JNIEnv的類型 typedef const struct JNIInvokeInterface* JavaVM; #endif
在C中,我們能夠看到JNIEnv的類型就是JNINativeInterface* 。是一個指針類型,那么在C++中呢,_JNIEnv是什么樣的呢?
struct _JNIEnv { /* do not rename this; it does not seem to be entirely opaque */ const struct JNINativeInterface* functions;
struct JNINativeInterface { void* reserved0; void* reserved1; void* reserved2; void* reserved3; jint (*GetVersion)(JNIEnv *); jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*, jsize); jclass (*FindClass)(JNIEnv*, const char*); jmethodID (*FromReflectedMethod)(JNIEnv*, jobject); jfieldID (*FromReflectedField)(JNIEnv*, jobject); /* spec doesn't show jboolean parameter */ jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);
所以。可以這樣理解,事實上JNIEnv,就是對DVM執行環境中C/C++函數的一個引用。而也正由於此,當C/C++想要在DVM中調用函數的時候。由於其是在DVM的環境中,所以它們必須通過JNIEnv* 這個參數來獲得這些方法,之后才可以使用。
那么這個JNIEnv是什么時候產生的呢?
當Android中第一個Java線程要調用本地的C/C++代碼的時候。DVM就會為該線程產生一個JNIEnv*的指針。
而每個線程在和C/C++互相調用的時候。其相應的JNIEnv 也是相互獨立。