android jni机制

来源:互联网 发布:程序员入门教程百度云 编辑:程序博客网 时间:2024/06/03 10:31
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
  1. public class MediaPlayer    
  2.  {    
  3.      ...    
  4.      
  5.      static {    
  6.          System.loadLibrary("media_jni");    
  7.          native_init();    
  8.      }    
  9.      
  10.       ...    
  11.      
  12.      private native final void native_setup(Object mediaplayer_this);    
  13.           
  14.       ...    
  15.   }  


可以看到上面的代码中,在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
  1. jint JNI_OnLoad(JavaVM* vm, void* reserved)  
  2. {  
  3.     JNIEnv* env = NULL;  
  4.     jint result = -1;  
  5.     //判断一下JNI的版本   
  6.     if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
  7.         LOGE("ERROR: GetEnv failed\n");  
  8.         goto bail;  
  9.     }  
  10.     assert(env != NULL);  
  11.   
  12.     if (register_android_media_MediaPlayer(env) < 0) {  
  13.         LOGE("ERROR: MediaPlayer native registration failed\n");  
  14.         goto bail;  
  15.     }  
  16.   
  17.     if (register_android_media_MediaRecorder(env) < 0) {  
  18.         LOGE("ERROR: MediaRecorder native registration failed\n");  
  19.         goto bail;  
  20.     }  
  21.   
  22.     if (register<span style="font-size:16px;">_android_media_MediaScanner(env) < 0) {  
  23.         LOGE("ERROR: MediaScanner native registration failed\n");  
  24.         goto bail;  
  25.     }</span>  
  26.   
  27.     if (register_android_media_MediaMetadataRetriever(env) < 0) {  
  28.         LOGE("ERROR: MediaMetadataRetriever native registration failed\n");  
  29.         goto bail;  
  30.     }  
  31.   
  32.     if (register_android_media_AmrInputStream(env) < 0) {  
  33.         LOGE("ERROR: AmrInputStream native registration failed\n");  
  34.         goto bail;  
  35.     }  
  36.   
  37.     if (register_android_media_ResampleInputStream(env) < 0) {  
  38.         LOGE("ERROR: ResampleInputStream native registration failed\n");  
  39.         goto bail;  
  40.     }  
  41.   
  42.     if (register_android_media_MediaProfiles(env) < 0) {  
  43.         LOGE("ERROR: MediaProfiles native registration failed");  
  44.         goto bail;  
  45.     }  
  46.   
  47.     /* success -- return valid version number */  
  48.     result = JNI_VERSION_1_4;  
  49.   
  50. bail:  
  51.     return result;  
  52. }  
上面这段代码的JNI_OnLoad(JavaVM* vm, void* reserved)函数实现与libmedia_jni.so库中。上面的代码中调用了一些形如register_android_media_MediaPlayer(env)的函数,这些函数的作用是注册native method。我们来看看函数register_android_media_MediaPlayer(env)的实现。

[java] view plaincopy
  1. // This function only registers the native methods  
  2. static int register_android_media_MediaPlayer(JNIEnv *env)  
  3. {  
  4.     return AndroidRuntime::registerNativeMethods(env,  
  5.                 "android/media/MediaPlayer", gMethods, NELEM(gMethods));  
[java] view plaincopy
  1. /* 
  2.  * Register native methods using JNI. 
  3.  */  
  4. /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,  
  5.     const char* className, const JNINativeMethod* gMethods, int numMethods)  
  6. {  
  7.     return jniRegisterNativeMethods(env, className, gMethods, numMethods);  
  8. }  
最终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类型的指针gMethodsgMethods指向一个JNINativeMethod数组,我们先看看JNINativeMethod这个结构体。

[java] view plaincopy
  1. typedef struct {  
  2.  const char* name; /*Java 中函数的名字*/  
  3.  const char* signature; /*描述了函数的参数和返回值*/  
  4.  void* fnPtr; /*函数指针,指向 C 函数*/  
  5.  } JNINativeMethod;  
再来看看gMethods数组

[java] view plaincopy
  1. static JNINativeMethod gMethods[] = {  
  2.     {"setDataSource",       "(Ljava/lang/String;)V",            (void *)android_media_MediaPlayer_setDataSource},  
  3.     。。。  
  4.     {"setAuxEffectSendLevel""(F)V",                           (void *)android_media_MediaPlayer_setAuxEffectSendLevel},  
  5.     {"attachAuxEffect",     "(I)V",                             (void *)android_media_MediaPlayer_attachAuxEffect},  
  6.     {"getOrganDBIndex",     "(II)I",                            (void *)android_media_MediaPlayer_getOrganDBIndex},  
  7. };  
d,int numMethods,不解释。


这样一来就完成了java native函数到到JNI层函数的映射。当然具体功能实现还是由JNI层函数来调用C/C++相应的功能函数



当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类型标示)返回值类型标示

[java] view plaincopy
  1. “()V”  
  2. "(II)V"  
  3. “(Ljava/lang/String;Ljava/lang/String)V";  


        实际上这些字符是与函数的参数类型一一对应的。
        “()” 中的字符表示参数,后面的则代表返回值。例如”()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”


        具体的对应关系见下面两张图:

 


        数组则以”["开始,用两个字符表示


         以上都是基本数据类型,前面我们解决了JNI函数的注册问题,下面我们来考虑这样一个问题。在java中调用native函数传递的参数是java数据类型,那么这些参数类型到了JNI会变成什么呢? 下面我们引出了一个新的话题——数据类型转换。


数据类型转换:

       在java层调用native函数传递到JNI层的参数,JNI层会做一些特殊处理,我们知道java数据类型分为基本数据类型和引用数据类型两种,JNI层也是区别对待的。下表示出了java数据类型—>native类型的转换。


         其中在java数据类型中,除了java中基本数据类型和数组,Class,String,Throwable,其余所有的java对象的数据类型在JNI中用jobject表示。下面来看一段代码

[java] view plaincopy
  1. {"native_invoke",       "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},  
[java] view plaincopy
  1. //java层native_invoke函数有两个参数都是Parcel  
  2. private native final int native_invoke(Parcel request, Parcel reply);  
  3.   
  4. //JNI层对应的函数android_media_MediaPlayer_invoke的最后两个参数与native_invoke的参数对应  
  5. android_media_MediaPlayer_invoke(JNIEnv *env, jobject thiz,  
  6.                                  jobject java_request, jobject java_reply)  


        从上面的代码可以看出来,java中的数据类型Parcel在JNI层对应的数据类型为jobejct,在JNI层的对应函数中,我们看到相对java层的native函数来说,多了两个参数JNIEnv *env ,jobject thiz。其中JNIEnv的定义在前一篇blog我们已经介绍过了。第二个参数jobject代表了java层的MediaPlayer对象,它表示在哪个MediaPlayer对象上调用的native_invoke。如果java层是static函数,那么这个参数将是jclass,表示是在调用那个java Class的静态函数。

        还记得前面我们说过,java层和JNI层应该是可以互相交互,我们通过java层中的native函数可以进入到JNI层,那么JNI层的代码能不能操作java层中函数呢?当然可以,通过JNIEnv


JNIEnv再度解析

         先来看看两个函数原型

[java] view plaincopy
  1. <span style="color:#000000;">jfieldID GetFieldID(jclass clazz,const char *name,const char *sig );  
  2. jmethodID GetMethodID(jclass clazz,const char *name,const char *sig);</span>  

         结合前面的知识来看,JNIEnv是一个与线程相关的代表JNI环境的结构体。JNIEnv实际上提供了一些JNI系统函数。通过这些系统函数可以调用java层中的函数或者操作jobect。下面我看一段函数

[java] view plaincopy
  1. class MyMediaScannerClient : public MediaScannerClient  
  2. {  
  3. public:  
  4.     MyMediaScannerClient(JNIEnv *env, jobject client)  
  5.         :   mEnv(env),  
  6.             mClient(env->NewGlobalRef(client)),  
  7.             mScanFileMethodID(0),  
  8.             mHandleStringTagMethodID(0),  
  9.             mSetMimeTypeMethodID(0)  
  10.     {  
  11.         jclass mediaScannerClientInterface = env->FindClass("android/media/MediaScannerClient");  
  12.         if (mediaScannerClientInterface == NULL) {  
  13.             fprintf(stderr, "android/media/MediaScannerClient not found\n");  
  14.         }  
  15.         else {  
  16.             mScanFileMethodID = env->GetMethodID(mediaScannerClientInterface, "scanFile",  
  17.                                                      "(Ljava/lang/String;JJ)V");  
  18.             mHandleStringTagMethodID = env->GetMethodID(mediaScannerClientInterface, "handleStringTag",  
  19.                                                      "(Ljava/lang/String;Ljava/lang/String;)V");  
  20.             mSetMimeTypeMethodID = env->GetMethodID(mediaScannerClientInterface, "setMimeType",  
  21.                                                      "(Ljava/lang/String;)V");  
  22.             mAddNoMediaFolderMethodID = env->GetMethodID(mediaScannerClientInterface, "addNoMediaFolder",  
  23.                                                      "(Ljava/lang/String;)V");  
  24.         }  
  25.     }  
  26. ...  
  27.   
  28. // returns true if it succeeded, false if an exception occured in the Java code  
  29.     virtual bool scanFile(const char* path, long long lastModified, long long fileSize)  
  30.     {  
  31.         jstring pathStr;  
  32.         if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;  
  33.   
  34.         mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);  
  35.   
  36.         mEnv->DeleteLocalRef(pathStr);  
  37.         return (!mEnv->ExceptionCheck());  
  38.     }  

可以看到上面的代码中,先找到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。看下面的具体代码

[java] view plaincopy
  1. int register_android_backup_BackupHelperDispatcher(JNIEnv* env)  
  2. {  
  3.     jclass clazz;  
  4.   
  5.     clazz = env->FindClass("java/io/FileDescriptor");  
  6.     LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");  
  7.     s_descriptorField = env->GetFieldID(clazz, "descriptor""I");  
  8.     LOG_FATAL_IF(s_descriptorField == NULL,  
  9.             "Unable to find descriptor field in java.io.FileDescriptor");  
  10.       
  11.     clazz = env->FindClass("android/app/backup/BackupHelperDispatcher$Header");  
  12.     LOG_FATAL_IF(clazz == NULL,  
  13.             "Unable to find class android.app.backup.BackupHelperDispatcher.Header");  
  14.     s_chunkSizeField = env->GetFieldID(clazz, "chunkSize""I");  
  15.     LOG_FATAL_IF(s_chunkSizeField == NULL,  
  16.             "Unable to find chunkSize field in android.app.backup.BackupHelperDispatcher.Header");  
  17.     s_keyPrefixField = env->GetFieldID(clazz, "keyPrefix""Ljava/lang/String;");  
  18.     LOG_FATAL_IF(s_keyPrefixField == NULL,  
  19.             "Unable to find keyPrefix field in android.app.backup.BackupHelperDispatcher.Header");  
  20.       
  21.     return AndroidRuntime::registerNativeMethods(env, "android/app/backup/BackupHelperDispatcher",  
  22.             g_methods, NELEM(g_methods));  
  23. }  
获得jfieldID之后呢,我们就可以在JNI层之间来访问和操作java层的field的值了,方法如下
[java] view plaincopy
  1. NativeType Get<type>Field(JNIEnv *env,jobject object,jfieldID fieldID)  
  2.   
  3. 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对象转换诚本地字符串。下面我们来看段事例代码。

[java] view plaincopy
  1. virtual bool scanFile(const char* path, long long lastModified, long long fileSize)  
  2.     {  
  3.         jstring pathStr;  
  4.         //将char*数组字符串转换诚jstring类型  
  5.         if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;  
  6.   
  7.         mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);  
  8.   
  9.         mEnv->DeleteLocalRef(pathStr);  
  10.         return (!mEnv->ExceptionCheck());  
  11.     }  
  12.   
  13.       ....  
  14.       ....  
  15. while (env->CallBooleanMethod(iter, hasNext)) {  
  16.             jobject entry = env->CallObjectMethod(iter, next);  
  17.             jstring key = (jstring) env->CallObjectMethod(entry, getKey);  
  18.             jstring value = (jstring) env->CallObjectMethod(entry, getValue);  
  19.   
  20.             const char* keyStr = env->GetStringUTFChars(key, NULL);  
  21.             ...  
  22.             ...  
GetStringUTFChars()函数将jstring类型转换成一个UTF-8本地字符串,另外如果代码中调用了上面的几个函数,则在做完相关工作后,要调用ReleaseStringChars函数或者ReleaseStringUTFChars函数来释放资源。看下面的代码
[java] view plaincopy
  1.             ...  
  2.             ...  
  3.             jstring key = (jstring) env->CallObjectMethod(entry, getKey);  
  4.             jstring value = (jstring) env->CallObjectMethod(entry, getValue);  
  5.   
  6.             const char* keyStr = env->GetStringUTFChars(key, NULL);  
  7.             if (!keyStr) {  // Out of memory  
  8.                 jniThrowException(  
  9.                         env, "java/lang/RuntimeException""Out of memory");  
  10.                 return;  
  11.             }  
  12.   
  13.             const char* valueStr = env->GetStringUTFChars(value, NULL);  
  14.             if (!valueStr) {  // Out of memory  
  15.                 jniThrowException(  
  16.                         env, "java/lang/RuntimeException""Out of memory");  
  17.                 return;  
  18.             }  
  19.   
  20.             headersVector.add(String8(keyStr), String8(valueStr));  
  21.   
  22.             env->DeleteLocalRef(entry);  
  23.             env->ReleaseStringUTFChars(key, keyStr);  
  24.             env->DeleteLocalRef(key);  
  25.             ...  
  26.             ...  
可以看到GetStringUTFChars与下面的ReleaseStringUTFChars对应。

转载自:http://blog.csdn.net/mci2004/article/details/7219140


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 事业单位考察档案丢了怎么办 当兵政审家访家里没人在家怎么办 士兵转业结婚材料不全怎么办 体育生训练腿疼怎么办 车底盘刮的严重怎么办 新车底盘被刮了怎么办 车侧面刮凹了怎么办 憋气久了想呕吐怎么办 19岁网贷欠了3万怎么办 大学生欠2w网贷怎么办 当兵去了网贷怎么办 考公安视力不过关怎么办 身份证号和姓名电话泄露了怎么办 黑色裙子被染色了怎么办 戴墨镜鼻子太塌怎么办 戴墨镜鼻子有印怎么办 戴眼镜鼻梁塌了怎么办 站的时间长了腿疼怎么办 小孩腿筋拉伤了怎么办 走多了小腿骨疼怎么办 走多了一个腿疼怎么办 走太多路腿酸痛怎么办 老年人脚肿并痛怎么办 孩子蛙跳肌后大腿痛怎么办 走路久了腿酸怎么办 走路多了膝盖痛怎么办 走路多了小腿痛怎么办 走多了腿疼怎么办 小腿走多了酸痛怎么办 腿肚受凉了酸痛怎么办 晚上腿疼的睡不着觉怎么办 走路走多了腿酸怎么办 跳完蛙跳大腿疼怎么办 走路走多了脚疼怎么办 走太多路脚酸痛怎么办 走路走的腿酸痛怎么办 路走得太多腿疼怎么办 走路走的足弓疼怎么办 走路走多了膝盖怎么办 2岁宝宝走路一只脚内八字怎么办 6岁宝宝o型腿怎么办