JNI是Java Native Interface的縮寫,JNI是一種機制,有了它就能夠在java程序中調用其餘native代碼,或者使native代碼調用java層的代碼。也就是說,有了JNI咱們可使Android項目中,java層與native層各自發揮所長並相互配合。 java
JAVA android
| | 數組
JNI app
| | ide
NATIVE 函數
JNI相對與native層來講是一個接口,java層的程序想訪問native層,必須經過JNI,反過來也同樣。 this
1,如何告訴VM(虛擬機)java層須要調用native層的哪些libs? spa
咱們知道java程序是運行在VM上的,而Native層的libs則否則。因此爲了讓java層能訪問native層的libs,必須得告訴VM要使用哪些native層的libs。下面看一段代碼 .net
- 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++(或者其餘語言)實現的具體函數呢?
當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方法。下面看一段代碼:
- 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)的實現。
- // 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));
- /*
- * 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:
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.)
b,char* className,這個沒什麼好說的,java空間中類名,其中包含了包名。
c,JNINativeMethod* gMethods,傳遞進去的是一個JNINativeMethod類型的指針gMethods,gMethods指向一個JNINativeMethod數組,咱們先看看JNINativeMethod這個結構體。
- typedef struct {
- const char* name; /*Java 中函數的名字*/
- const char* signature; /*描述了函數的參數和返回值*/
- void* fnPtr; /*函數指針,指向 C 函數*/
- } JNINativeMethod;
再來看看gMethods數組
- 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},
- };
當VM載入libxxx_jni.so這個庫時,就會呼叫JNI_OnLoad()函數。在JNI_OnLoad()中註冊本地函數,繼續調用到AndroidRuntime::registerNativeMethods(),該函數向VM(即AndroidRuntime)註冊gMethods[]數組中包含的本地函數了。AndroidRuntime::registerNativeMethods()起到了如下兩個做用:
1,registerNativeMethods()函數使得java空間中的Native函數更加容易的找到對應的本地函數。(經過gMethods[]中的函數指針)
2,能夠在執行期間進行本地函數的替換。由於gMethods[]數組是一個<java中函數名字,本地函數指針>的對應表,因此能夠在程序的執行過程當中,屢次呼叫registerNativeMethods()函數來更換本地函數的指針,提升程序的彈性。
函數簽名:
在JNINativeMethod的結構體中,有一個描述函數的參數和返回值的簽名字段,它是java中對應函數的簽名信息,由參數類型和返回值類型共同組成。這個函數簽名信息的做用是什麼呢?
因爲java支持函數重載,也就是說,能夠定義同名但不一樣參數的函數。然而僅僅根據函數名是無法找到具體函數的。爲了解決這個問題,JNI技術中就將參數類型和返回值類型的組合做爲一個函數的簽名信息,有了簽名信息和函數名,就能順利的找到java中的函數了。
JNI規範定義的函數簽名信息格式以下:
(參數1類型標示參數2類型標示......參數n類型標示)返回值類型標示
實際上這些字符是與函數的參數類型一一對應的。
「()」 中的字符表示參數,後面的則表明返回值。例如」()V」 就表示 void Func();
「(II)V」 表示 void Func(int, int);
值得注意的一點是,當參數類型是引用數據類型時,
其格式是「L包名;」其中包名中的「.」換成「/」,
因此在上面的例子中(Ljava/lang/String;Ljava/lang/String;)V 表示 void Func(String,String);
若是 JAVA 函數位於一個嵌入類,則用$做爲類名間的分隔符。
例如 「(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z」
V |
void |
void |
Z |
jboolean |
boolean |
I |
jint |
int |
J |
jlong |
long |
D |
jdouble |
double |
F |
jfloat |
float |
B |
jbyte |
byte |
C |
jchar |
char |
S |
jshort |
short |
|
[I |
jintArray |
int[] |
[F |
jfloatArray |
float[] |
[B |
jbyteArray |
byte[] |
[C |
jcharArray |
char[] |
[S |
jshortArray |
short[] |
[D |
jdoubleArray |
double[] |
[J |
jlongArray |
long[] |
[Z |
jbooleanArray |
Boolean[] |
數據類型轉換:
在java層調用native函數傳遞到JNI層的參數,JNI層會作一些特殊處理,咱們知道java數據類型分爲基本數據類型和引用數據類型兩種,JNI層也是區別對待的。下表示出了java數據類型—>native類型的轉換。
其中在java數據類型中,除了java中基本數據類型和數組,Class,String,Throwable,其他全部的java對象的數據類型在JNI中用jobject表示。下面來看一段代碼
- {"native_invoke", "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},
- //java層native_invoke函數有兩個參數都是Parcel
- private native final int native_invoke(Parcel request, Parcel reply);
-
- //JNI層對應的函數android_media_MediaPlayer_invoke的最後兩個參數與native_invoke的參數對應
- android_media_MediaPlayer_invoke(JNIEnv *env, jobject thiz,
- jobject java_request, jobject java_reply)
從上面的代碼能夠看出來,java中的數據類型Parcel在JNI層對應的數據類型爲jobejct,在JNI層的對應函數中,咱們看到相對java層的native函數來講,多了兩個參數JNIEnv *env ,jobject this。第二個參數jobject表明了java層的MediaPlayer對象,它表示在哪一個MediaPlayer對象上調用的native_invoke。若是java層是static函數,那麼這個參數將是jclass,表示是在調用那個java Class的靜態函數。
還記得前面咱們說過,java層和JNI層應該是能夠互相交互,咱們經過java層中的native函數能夠進入到JNI層,那麼JNI層的代碼能不能操做java層中函數呢?固然能夠,經過JNIEnv
JNIEnv再度解析
先來看看兩個函數原型
- <span style="color:#000000;">jfieldID GetFieldID(jclass clazz,const char *name,const char *sig );
- jmethodID GetMethodID(jclass clazz,const char *name,const char *sig);</span>
結合前面的知識來看,JNIEnv是一個與線程相關的表明JNI環境的結構體。JNIEnv實際上提供了一些JNI系統函數。經過這些系統函數能夠調用java層中的函數或者操做jobect。下面我看一段函數
- class MyMediaScannerClient : public MediaScannerClient
- {
- public:
- MyMediaScannerClient(JNIEnv *env, jobject client)
- : mEnv(env),
- mClient(env->NewGlobalRef(client)),
- mScanFileMethodID(0),
- mHandleStringTagMethodID(0),
- mSetMimeTypeMethodID(0)
- {
- jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");
- if (mediaScannerClientInterface == NULL) {
- fprintf(stderr, "android/media/MediaScannerClient not found\n");
- }
- else {
- mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",
- "(Ljava/lang/String;JJ)V");
- mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",
- "(Ljava/lang/String;Ljava/lang/String;)V");
- mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",
- "(Ljava/lang/String;)V");
- mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder",
- "(Ljava/lang/String;)V");
- }
- }
- ...
-
- // returns true if it succeeded, false if an exception occured in the Java code
- virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
- {
- jstring pathStr;
- if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
-
- mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
-
- mEnv->DeleteLocalRef(pathStr);
- return (!mEnv->ExceptionCheck());
- }
能夠看到上面的代碼中,先找到java層中MediaScannerClinet類在JNI層中對應的jclass實例(經過FindClass)。而後拿到MediaScannerclient類中所須要用到函數的函數函數id(經過GetMethodID)。接着經過JNIEnv調用CallXXXMethod函數而且把對應的jobject,jMethodID還有對應的參數傳遞進去,這樣的經過CallXXXMethod就完成了JNI層向java層的調用。這裏要注意一點的是這裏JNI層中調用的方法其實是java中對象的成員函數,若是要調用static函數可使用CallStaticXXXMethod。這種機制有利於native層回調java代碼完成相應操做。
上面講述了以下在JNI層中去調用java層的代碼,那麼理所固然的應該能夠在JNI層中訪問或者修改java層中某對象的成員變量的值。咱們經過JNIEnv中的GetFieldID()函數來獲得java中對象的某個域的id。看下面的具體代碼
- int register_android_backup_BackupHelperDispatcher(JNIEnv* env)
- {
- jclass clazz;
-
- clazz = env->FindClass("java/io/FileDescriptor");
- LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");
- s_descriptorField = env->GetFieldID(clazz, "descriptor", "I");
- LOG_FATAL_IF(s_descriptorField == NULL,
- "Unable to find descriptor field in java.io.FileDescriptor");
-
- clazz = env->FindClass("android/app/backup/BackupHelperDispatcher$Header");
- LOG_FATAL_IF(clazz == NULL,
- "Unable to find class android.app.backup.BackupHelperDispatcher.Header");
- s_chunkSizeField = env->GetFieldID(clazz, "chunkSize", "I");
- LOG_FATAL_IF(s_chunkSizeField == NULL,
- "Unable to find chunkSize field in android.app.backup.BackupHelperDispatcher.Header");
- s_keyPrefixField = env->GetFieldID(clazz, "keyPrefix", "Ljava/lang/String;");
- LOG_FATAL_IF(s_keyPrefixField == NULL,
- "Unable to find keyPrefix field in android.app.backup.BackupHelperDispatcher.Header");
-
- return AndroidRuntime::registerNativeMethods(env, "android/app/backup/BackupHelperDispatcher",
- g_methods, NELEM(g_methods));
- }
得到jfieldID以後呢,咱們就能夠在JNI層之間來訪問和操做java層的field的值了,方法以下
- NativeType Get<type>Field(JNIEnv *env,jobject object,jfieldID fieldID)
-
- void Set<type>Field(JNIEnv *env,jobject object ,jfieldID fieldID,NativeType value)
注意這裏的NativeType值得是jobject,jboolean等等。
如今咱們看到有了JNIEnv,咱們能夠很輕鬆的操做jobject所表明的java層中的實際的對象了。
jstring介紹
之因此要把jstring單獨拿出來講正是因爲它的特殊性。java中String類型也是一個引用類型,可是JNI中並無用jobject來與之對應,JNI中單首創建了一個jstring類型來表示java中的String類型。顯然java中的String不能和C++中的String等同起來,那麼怎麼操做jstring呢?方法不少下面看幾個簡單的方法
1,調用JNIEnv的NewStringUTF將根據Native的一個UTF-8字符串獲得一個jstring對象。只有這樣才能讓一個C++中String在JNI中使用。
2,調用JNIEnv的GetStringChars函數(將獲得一個Unicode字符串)和GetStringUTFChars函數(將獲得一個UTF-8字符串),他們能夠將java String對象轉換誠本地字符串。下面咱們來看段事例代碼。
- virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
- {
- jstring pathStr;
- //將char*數組字符串轉換誠jstring類型
- if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;
-
- mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
-
- mEnv->DeleteLocalRef(pathStr);
- return (!mEnv->ExceptionCheck());
- }
-
- ....
- ....
- while (env->CallBooleanMethod(iter, hasNext)) {
- jobject entry = env->CallObjectMethod(iter, next);
- jstring key = (jstring) env->CallObjectMethod(entry, getKey);
- jstring value = (jstring) env->CallObjectMethod(entry, getValue);
-
- const char* keyStr = env->GetStringUTFChars(key, NULL);
- ...
- ...
GetStringUTFChars()函數將jstring類型轉換成一個UTF-8本地字符串,另外若是代碼中調用了上面的幾個函數,則在作完相關工做後,要調用ReleaseStringChars函數或者ReleaseStringUTFChars函數來釋放資源。看下面的代碼
- ...
- ...
- jstring key = (jstring) env->CallObjectMethod(entry, getKey);
- jstring value = (jstring) env->CallObjectMethod(entry, getValue);
-
- const char* keyStr = env->GetStringUTFChars(key, NULL);
- if (!keyStr) { // Out of memory
- jniThrowException(
- env, "java/lang/RuntimeException", "Out of memory");
- return;
- }
-
- const char* valueStr = env->GetStringUTFChars(value, NULL);
- if (!valueStr) { // Out of memory
- jniThrowException(
- env, "java/lang/RuntimeException", "Out of memory");
- return;
- }
-
- headersVector.add(String8(keyStr), String8(valueStr));
-
- env->DeleteLocalRef(entry);
- env->ReleaseStringUTFChars(key, keyStr);
- env->DeleteLocalRef(key);
- ...
- ...
能夠看到GetStringUTFChars與下面的ReleaseStringUTFChars對應。
參考: http://blog.csdn.net/mci2004/article/details/7211678
http://blog.csdn.net/mci2004/article/details/7219140