关闭

ANDROID jni 中的事件回调机制JNIenv的使用

747人阅读 评论(0) 收藏 举报
分类:
android framework 里java调用native,使用JNI机制,java如何调用native,在framework里面的例子很多,有很多参考,可以方便的使用。
但是在一些native中如果涉及到了事件回调,需要在native里调用java对象,在framework 框架里也有这样的例子。
在项目里用到了这一机制。

在native 注册的时候首先保存java的调用方法:
static void 
net_sunniwell_SWProxy_native_init(JNIEnv *env)
{
     LOGE("SWProxy  init\n");

     jclass clazz;
     clazz = env->FindClass("net/sunniwell/media/SWProxy");
     if (clazz == NULL) {
          return;
     }

     fields.post_event = env->GetStaticMethodID(clazz, "postEvent",
               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
     if (fields.post_event == NULL) {
          LOGE("SWProxy  init find postEvent NULL\n");
          return;
     }

     LOGE("SWProxy init find postEvent\n");
}

JNI中调用Java中的方法
void JNISWProxyListener::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
               msg, ext1, ext2, NULL);
}

这个格式在framework框架中是一个普遍使用的形式。
但是在实际的使用中却经常在env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, NULL)挂掉。

JNIenv 是和线程有关的变量,JVM是和进程有关的变量:在native事件回调中,当调用到notify时,我们获取JNIEnv *env = AndroidRuntime::getJNIEnv(); 但是这个env和当前的线程有没有关联是不能确定的问题,如果native在另外的线程里处理事件回调,这个env就和JNI调用的env共用了。
在正常的JNI调用中JNIENV是由jvm 传递进来的,jni函数的第一个参数就是JNIEnv。如下:
static void 
net_sunniwell_SWProxy_native_finalize( JNIEnv *env )
{
     LOGE("SWProxy  finalize\n");
     http_server_set_eventcallback(0);
}

查阅了一些资料:
There are certain constraints that you must keep in mind when writing native methods that are to run in a multithreaded environment. By understanding and programming within these constraints, your native methods will execute safely no matter how many threads simultaneously execute a given native method. For example:
A JNIEnv pointer is only valid in the thread associated with it. You must not pass this pointer from one thread to another, or cache and use it in multiple threads. The Java virtual machine passes a native method the same JNIEnv pointer in consecutive invocations from the same thread, but passes different JNIEnv pointers when invoking that native method from different threads. Avoid the common mistake of caching the JNIEnv pointer of one thread and using the pointer in another thread.
Local references are valid only in the thread that created them. You must not pass local references from one thread to another. You should always convert local references to global references whenever there is a possibility that multiple threads may use the same reference.

env是线程相关的,env只能在创建它的线程中使用
下面提出了一个解决办法:
env是线程相关的,JVM却是进程相关的。我们可以通过JVM来获取线程相关的JNIENV。
JNIEnv* 
A JNI interface pointer (JNIEnv*) is passed as an argument for each native function mapped to a Java method, allowing for interaction with the JNI environment within the native method. This JNI interface pointer can be stored, but remains valid only in the current thread. Other threads must first call AttachCurrentThread() to attach themselves to the VM and obtain a JNI interface pointer. Once attached, a native thread works like a regular Java thread running within a native method. The native thread remains attached to the VM until it calls DetachCurrentThread() to detach itself. [3] 
//To attach to the current thread and get a JNI interface pointer: 
JNIEnv *env; 
(*g_vm)->AttachCurrentThread (g_vm, (void **) &env, NULL); 
//To detach from the current thread: 
(*g_vm)->DetachCurrentThread (g_vm);

下面是修改以后的代码:

static void 
net_sunniwell_SWProxy_native_init(JNIEnv *env)
{
     LOGE("SWProxy  init\n");

     jclass clazz;
     clazz = env->FindClass("net/sunniwell/media/SWProxy");
     if (clazz == NULL) {
          return;
     }

     fields.post_event = env->GetStaticMethodID(clazz, "postEvent",
               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
     if (fields.post_event == NULL) {
          LOGE("SWProxy  init find postEvent NULL\n");
          return;
     }

     // Set the virtual machine.
     env->GetJavaVM(&(fields.pVM));
     LOGE("SWProxy  init find postEvent\n");
}

void JNISWProxyListener::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
     JNIEnv *env ;
     fields.pVM->AttachCurrentThread(&env, NULL);
     env->CallStaticVoidMethod(mClass, fields.post_event, mObject, msg, ext1, ext2, NULL);
     fields.pVM->DetachCurrentThread();
}

修改以后是可以了,但是却有一个疑问:为什么在framework里面第一种调用方法却是OK的,仔细思考以后,原来和android 的binder机制有关系。binder分为代理端和服务端,在jni中运行的是代理端,当服务端的事件回调被调用时,通过binder跨进程通知代理端,这样代理端还是运行在java JNI 的线程中,所以不会出现JNIenv被多线程共用的情况。
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:37963次
    • 积分:458
    • 等级:
    • 排名:千里之外
    • 原创:90篇
    • 转载:10篇
    • 译文:0篇
    • 评论:1条
    文章分类
    最新评论