jni是Java Native Interface的缩写,是链接Java层和Native层的桥梁,提供了Java层和Native层(C/C++)相互调用的能力。
Java世界的代码要怎么使用Native世界的代码呢,这就需要一个桥梁来将它们连接在一起,而JNI就是这个桥梁。
以Android中的MediaRecorder为例:
java层的代码:
public class MediaRecorder { static { System.loadLibrary("media_jni");//1 native_init();//2 } ... private static native final void native_init();//3 ... public native void start() throws IllegalStateException; ... }jni层的代码:
static void android_media_MediaRecorder_native_init(JNIEnv *env) { jclass clazz; clazz = env->FindClass("android/media/MediaRecorder"); if (clazz == NULL) { return; } ... fields.post_event = env->GetStaticMethodID( clazz, "postEventFromNative","(Ljava/lang/Object;IIILjava/lang/Object;)V"); if (fields.post_event == NULL) { return; } } static void android_media_MediaRecorder_start(JNIEnv *env, jobject thiz) { ALOGV("start"); sp<MediaRecorder> mr = getMediaRecorder(env, thiz); process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed."); }android_media_MediaRecorder_native_init方法是native_init方法在JNI层的实现 ,android_media_MediaRecorder_start同理。
JNI方法注册分为静态注册和动态注册,其中静态注册多用于NDK开发,而动态注册多用于Framework开发。
当调用java层的native_init()方法,由于声明了这个方法为native方法,所以会去jni中寻找对应的方法:android_media_MediaRecorder_native_init,找到后就会将两者建立关联,其实就是保存下jni层方法的指针,再次调用native_init()时便会直接调用这个方法指针。
弊端:
JNI层的方法名称过长。声明Native方法的类需要用javah生成头文件。初次调用JIN方法时需要建立关联(在jni层搜索对应的方法),影响效率。JNI中有一种结构用来记录Java的Native方法和JNI方法的关联关系,它就是JNINativeMethod,它在jni.h中被定义:
typedef struct { const char* name;//Java方法的名字 const char* signature;//Java方法的签名信息 void* fnPtr;//JNI中对应的方法指针 } JNINativeMethod;实现流程:
利用结构体 JNINativeMethod数组记录 java 方法与 JNI 函数的对应关系;
实现 JNI_OnLoad方法,在加载动态库后,执行动态注册;
调用 FindClass方法,获取 java 对象;
调用 RegisterNatives方法,传入 java 对象,以及JNINativeMethod数组,以及注册数目完成注册;
优点:
流程更加清晰可控效率更高JavaVM是java虚拟机在jni层的代表,一个进程只有一个JavaVM, 所以所有线程共享一个JavaVM。
JNIEnv表示java调用native语言的环境,是一个指向全部jni方法的指针。只在创建它的线程有效,不能跨线程传递。不同线程JNIEnv彼此独立。
获取JavaVM
//方式一 //通过JNIEnv获取JavaVM JNIEXPORT jstring JNICALL Java_com_don_ndk_NDKTest_getHello (JNIEnv *pEnv, jobject obj, jint param) { pEnv->GetJavaVM(&javaVM); jstring result = pEnv->NewStringUTF(getHello()); return result; } //方式二 //在加载动态链接库的时候,JVM会调用JNI_OnLoad(JavaVM* jvm, void* reserved)(如果定义了该函数),第一个参数会传入JavaVM指针。 JavaVM *javaVM = NULL; jint JNI_OnLoad(JavaVM *vm, void *reserved) { javaVM = vm; LOGI("JNI_OnLoad ndk test"); return JNI_VERSION_1_4; }获取JNIEnv
不同线程间的JNIEnv不同,换句话说,就是在线程A中的JNIEnv,在线程B中不能使用。 JavaVM一个进程只有一个,java代码调用c++代码的时候jni给了一个JNIEnv变量,但是如果在c++中新创建了一个子线程C用于处理任务,那么在子线程C中不能使用前面的那个JNIEnv。如果子线程中想要使用JNIEnv,只能通过JavaVM得到一个新的JNIEnv,在子线程中使用。
void threadTest() { pthread_create(&pthread, NULL, doThreadThing, NULL); } void *doThreadThing(void *data) { LOGI("doThreadThing ****************"); if (javaVM == NULL) { LOGW("doThreadThing invalid javaVM"); return NULL; } JNIEnv *jniEnv = NULL; jint ret = javaVM->GetEnv((void **) &jniEnv, JNI_VERSION_1_4); LOGI("doThreadThing ret: %d", ret); if (ret == JNI_EDETACHED) { if (javaVM->AttachCurrentThread(&jniEnv, NULL) != JNI_OK) { LOGW("doThreadThing AttachCurrentThread failed"); return NULL; } else { LOGI("doThreadThing AttachCurrentThread success"); } } if (jniEnv == NULL) { LOGW("doThreadThing invalid jniEnv"); return NULL; } else { LOGI("doThreadThing get JNIEnv success"); } calljstringMethod(jniEnv, jniObj); javaVM->DetachCurrentThread(); pthread_exit(&pthread); }