jni原理

mac2024-05-22  39

概念

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); }
最新回复(0)