现在的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),他们分别在程序的开始和结束的时候调用。我们可以把一些初始化和释放的工作在这两个方法中完成。
是不是很有意思?

本文深入探讨了Android应用中JNI调用机制,特别是如何在C/C++层回调Java方法。介绍了JNI Invocation API的使用方法,包括创建Java虚拟机、附加线程及释放资源的过程,并给出了具体的代码示例。
960

被折叠的 条评论
为什么被折叠?



