jni原理

JNI作为Java和Native层的桥梁,允许相互调用。本文介绍了JNI的概念,包括静态和动态注册方法,其中静态注册适用于NDK开发,而动态注册适合Framework开发。JavaVM代表Java虚拟机在JNI层,且一个进程只有一个,而JNIEnv是线程相关的,每个线程拥有独立的JNIEnv,不可跨线程使用。通过JavaVM可以获取JNIEnv实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概念

jni是Java Native Interface的缩写,是链接Java层和Native层的桥梁,提供了Java层和Native层(C/C++)相互调用的能力。

Java世界的代码要怎么使用Native世界的代码呢,这就需要一个桥梁来将它们连接在一起,而JNI就是这个桥梁。

VmSJBV.png

以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()时便会直接调用这个方法指针。

    弊端:

    1. JNI层的方法名称过长。
    2. 声明Native方法的类需要用javah生成头文件。
    3. 初次调用JIN方法时需要建立关联(在jni层搜索对应的方法),影响效率。
  • 动态注册

    JNI中有一种结构用来记录Java的Native方法和JNI方法的关联关系,它就是JNINativeMethod,它在jni.h中被定义:

    typedef struct {
        const char* name;//Java方法的名字
        const char* signature;//Java方法的签名信息
        void*       fnPtr;//JNI中对应的方法指针
    } JNINativeMethod;
    

    实现流程:

    1. 利用结构体 JNINativeMethod数组记录 java 方法与 JNI 函数的对应关系;

    2. 实现 JNI_OnLoad方法,在加载动态库后,执行动态注册;

    3. 调用 FindClass方法,获取 java 对象;

    4. 调用 RegisterNatives方法,传入 java 对象,以及JNINativeMethod数组,以及注册数目完成注册;

    优点:

    1. 流程更加清晰可控
    2. 效率更高
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);
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值