现在的Android应用,越来越多开始使用JNI调用了,把底层的复杂运算交给C/C++来完成,然后通过JNI来完成java与C/C++的连接。
在我开发一个这样的应用的过程中,遇到了一个回调的问题。除了在java层调用C的实现,有时候也需要在C层调用java的方法。比如,在C层实现的事件处理器需要在事件发生时,上抛事件,告诉java层。
这个过程和java调用C不一样,java中的调用是声明了native关键字的方法,在C层去实现他的方式来调用,他可以获得JNI interface 的指针(就是JNIEnv),可以获得虚拟机的上下文环境。而C中调用java中的方法是没有办法获得这些东西,必须通过java虚拟机来获得。Java提供了满足这种需求的API(Invocation API:http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html)。
Invocation API允许应用区加载Java虚拟机到任意一个native应用,比如:
#include <jni.h> /* where everything is defined */ ... JavaVM *jvm; /* denotes a Java VM */ JNIEnv *env; /* pointer to native method interface */ JDK1_1InitArgs vm_args; /* JDK 1.1 VM initialization arguments */ vm_args.version = 0x00010001; /* New in 1.1.2: VM version */ /* Get the default initialization arguments and set the class * path */ JNI_GetDefaultJavaVMInitArgs(&vm_args); vm_args.classpath = ...; /* load and initialize a Java VM, return a JNI interface * pointer in env */ JNI_CreateJavaVM(&jvm, &env, &vm_args); /* invoke the Main.test method using the JNI */ jclass cls = env->FindClass("Main"); jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V"); env->CallStaticVoidMethod(cls, mid, 100); /* We are done. */ jvm->DestroyJavaVM();
这是一个C++中获得虚拟机的example,是在JDK 1.1中实现的,现在已经跑不通了,经供参考,但是里面得到虚拟机原理是一样的。
它用到了3个方法: Create VM,Attaching VM,Unloading VM.
JNI_CreateJavaVM() 方法用来加载和初始化一个java虚拟机并且返回JNI接口的指针(JNIEnv), 这个线程被看做是主线程。
JNIEnv是在当前线程中是合法的,如果其他的线程去访问java虚拟机,他必须调用一下AttachCurrentThread()方法,把自己附属到VM中才能获得JNIEnv。一旦调用成功,native的线程就像一个普通的java线程运行在native的线程中(native中的线程都是Linux线程,由内核调用执行)。native线程仍然连接到VM,直到它调用DetachCurrentThread()来分离。
主线程不能从VM中分离自己,必须调用DestroyJavaVM()方法去卸载整个VM。
有了这些概念,就很容易理解整个问题了。来看一下我们的主要代码调用:
//declare java VM and jobject JavaVM* globalVM; jobject globalObj; //Get VM and JNIEnv at a native implemention method statement JNIEXPORT jint JNICALL Java_com_ericsson_mstv_client_upnp_api_UPnPCtrlPointNative_ctrlPointStart( JNIEnv *env, jobject jObj) { globalObj = (*env)->NewGlobalRef(env, jObj); (*env)->GetJavaVM(env, &globalVM); return startCtrlPoint(uPnPCallback,NULL); } //event handler void callback(....){ JNIEnv *env; if(!globalVM || !globalObj){ return; } int envState = (*globalVM)->GetEnv(globalVM, (void **) &env, JNI_VERSION_1_6); if (envState == JNI_EDETACHED) { envState = (*globalVM)->AttachCurrentThread(globalVM, &env, NULL); if (envState != 0) { //something log print return; } } else if (envState == JNI_EVERSION) { //something log print return; } //JNI data structure conversion ........ (*globalVM)->DetachCurrentThread(globalVM); } JNIEXPORT jint JNICALL Java_com_ericsson_mstv_client_upnp_api_UPnPCtrlPointNative_ctrlPointStop( JNIEnv *env, jobject jObj) { (*env)->DeleteGlobalRef(env, globalObj); globalVM = NULL; return stopCtrlPoint(); }
在这里用到了两个native方法,因为在在我们项目中的lifecycle的start和stop阶段调用,所以就把VM的一些逻辑放到里面来完成。其实Invocation API中提供了两个方法:jint JNI_OnLoad(JavaVM *vm, void *reserved) 和void JNI_OnUnload(JavaVM *vm, void *reserved),他们分别在程序的开始和结束的时候调用。我们可以把一些初始化和释放的工作在这两个方法中完成。
是不是很有意思?