概念
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方法注册
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和JNIEnv
-
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); }