JNI-静态注册和动态注册

静态注册

默认情况下,就是静态注册,静态注册是最简单的方式,NDK开发过程中,基本上使用静态注册。

新建一个Java工程。添加运行测试Java文件

image
public class TestDemo {

    public native String stringFromJNI(); // 静态注册

    public native void staticRegister(); // 静态注册


    public static void main(String[] args) {

        TestDemo demo = new TestDemo();
        System.out.println(demo.stringFromJNI());

    }
}

使用javah自动生成JNI C函数头文件

$ javah com.jni.dynamic.register.TestDemo
image
/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_jni_dynamic_register_TestDemo */

#ifndef _Included_com_jni_dynamic_register_TestDemo
#define _Included_com_jni_dynamic_register_TestDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_jni_dynamic_register_TestDemo
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_jni_dynamic_register_TestDemo_stringFromJNI
  (JNIEnv *, jobject);

/*
 * Class:     com_jni_dynamic_register_TestDemo
 * Method:    staticRegister
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jni_dynamic_register_TestDemo_staticRegister
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

JNIEXPORT jstring JNICALL Java_com_jni_dynamic_register_TestDemo_stringFromJNI(JNIEnv *, jobject);JNIEXPORT void JNICALL Java_com_jni_dynamic_register_TestDemo_staticRegister (JNIEnv *, jobject);

这种通过javah默认生成C函数方法声明的方式就是静态注册

通过Clion新建C++工程,com_jni_dynamic_register_TestDemo.h放入工程中

image
#include "com_jni_dynamic_register_TestDemo.h"
#include 
#include 

using namespace std;

JNIEXPORT jstring JNICALL Java_com_jni_dynamic_register_TestDemo_stringFromJNI
        (JNIEnv *env, jobject thiz) {
    std::string hello = "默认就是静态注册哦";
    cout << hello << endl;
    return env->NewStringUTF(hello.c_str());
}

JNIEXPORT void JNICALL Java_com_jni_dynamic_register_TestDemo_staticRegister
        (JNIEnv *env, jobject thiz) {

    cout << "staticRegister" << endl;

}

生成动态库

add_library(dynamicRegister SHARED dynamic_register.cpp)
image

个人使用的macOS 所以动态库是.dylib

"静态注册"方法确实帮我们省了很多事情,但是也有相应的缺点

  1. 首次调用 Java 的 native 方法,虚拟机会去搜寻对应的 Native 层的函数,这就有点影响执行效率了。如果搜索到了,就会建立映射关系,下次就不用再浪费时间去搜索了( 运行期 才会去 匹配JNI函数,性能上 低于 动态注册)。

  2. Native 层的函数名字太长,名字的格式为 Java_包名_类名_方法名,例如Java_com_jni_dynamic_register_TestDemo_staticRegister

有"静态注册",当然就有"动态注册",那么相比较而言,有哪些优缺点呢

  1. "动态注册"需要我们手动建立函数映射关系,虽然增加了代码量,但是可以提供运行效率。
  2. "动态注册"允许我们自定义函数名字。
  3. 相比于"静态注册",工作效率高。

虽然"动态注册"相比于"静态注册"有这么多好处,但是需要一定的学习成本,但是这也是非常值得的,那么我们就开始吧。

加载动态库

我们知道,在 Java 层通过 System.loadLibrary() 方法可以加载一个动态库,此时虚拟机就会调用JNI库中的 JNI_OnLoad() 函数(放在哪个文件无所谓),函数原型如下

#include 

jint JNI_OnLoad(JavaVM* vm, void* reserved);

参数介绍

  • vm: JavaVM 指针
  • reserved: 类型为 void *,这个参数是为了保留位置,以供将来使用。

返回值代表被动态库需要的JNI版本,当然,如果虚拟机无法识别这个返回的版本,那么动态库也加载不了。

目前已有的返回值有四个,分别为 JNI_VERSION_1_1, JNI_VERSION_1_2, JNI_VERSION_1_4, JNI_VERSION_1_6

如果动态库没有提供 JNI_OnLoad() 函数,虚拟机会假设动态库只需要 JNI_VERSION_1_1 版本即可,然而这个版本太旧,很多新的函数都没有,因此我们最好在动态库中提供这个函数,并返回比较新的版本,例如 JNI_VERSION_1_6

//====================dynamic_register.cpp==================
#include "com_jni_dynamic_register_TestDemo.h"
#include 
#include 

using namespace std;

JNIEXPORT jstring JNICALL Java_com_jni_dynamic_register_TestDemo_stringFromJNI
        (JNIEnv *env, jobject thiz) {
    std::string hello = "默认就是静态注册哦";
    cout << hello << endl;
    return env->NewStringUTF(hello.c_str());
}

JNIEXPORT void JNICALL Java_com_jni_dynamic_register_TestDemo_staticRegister
        (JNIEnv *env, jobject thiz) {

    cout << "staticRegister" << endl;

}

// JNI JNI_OnLoad函数,如果你不写JNI_OnLoad,默认就有JNI_OnLoad,如果你写JNI_OnLoad函数 覆写默认的JNI_OnLoad函数
extern "C"
JNIEXPORT jint JNI_OnLoad(JavaVM *javaVm, void *) {

    cout << "JNI_OnLoad" << endl;

    return JNI_VERSION_1_6; //  // AS的JDK在JNI默认最高1.6      存Java的JDKJNI 1.8
}
image

注册函数

JNI_OnLoad() 函数经常会用来做一些初始化操作,"动态注册"就是在这里进行的。

"动态注册"可以通过调用_JNIEnv结构体的 RegisterNatives() 函数

struct _JNIEnv {

    const struct JNINativeInterface* functions;

#if defined(__cplusplus)
    jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
        jint nMethods)
    { return functions->RegisterNatives(this, clazz, methods, nMethods); }
#endif /*__cplusplus*/
}

实际使用的函数原型如下

jint RegisterNatives(JNIEnv *env, jclass clazz, 
        const JNINativeMethod *methods, jint nMethods);

参数解释

  1. env: JNIEnv指针.
  2. clazz: 代表 Java 的一个类。
  3. methos: 代表结构体 JNINativeMethod 数组。JNINativeMethod结构体定了Java层的native方法和底层的函数的映射关系。
  4. nMethods: 代表第三个参数methods所指向的数组的大小。

返回值

0代表成功,负值代表失败。

JNINativeMethod结构体

RegisterNatives() 函数最重要的部分就是 JNINativeMethod 这个结构体,我们看下这个结构体声明

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

name表示Javanative方法的名字。

signature表示方法的签名。

fnPtr是一个函数指针,指向JNI层的一个函数,也就是和Java层的native建立映射关系的函数。

实现动态注册

有了前面的所有基础,那么我们就可以实现自己的动态注册功能了。

首先带有native方法的Java类如下

public class TestDemo {

    static {
        System.load("/Volumes/CodeApp/SourceWork/CPlus_workspace/JNI-Demo-Lib/cmake-build-debug/dynamic_register/libdynamicRegister.dylib");
    }

    public native String stringFromJNI(); // 静态注册

    public native void staticRegister(); // 静态注册

    public native void dynamicJavaMethod01(); // 动态注册1

    public native int dynamicJavaMethod02(String valueStr); // 动态注册2

    public static void main(String[] args) {



    }

}

根据头文件的注释和函数原型,我们就可以实现如下的动态注册

// native 真正的函数
// void dynamicMethod01(JNIEnv *env, jobject thiz) { // OK的
void dynamicMethod01() { // 也OK  如果你用不到  JNIEnv jobject ,可以不用写
    cout << "我是动态注册的函数 dynamicMethod01..." << endl;
}

int dynamicMethod02(JNIEnv *env, jobject thiz, jstring valueStr) { // 也OK
    const char *text = env->GetStringUTFChars(valueStr, nullptr);
    cout << "我是动态注册的函数 dynamicMethod02... " << text << endl;
    env->ReleaseStringUTFChars(valueStr, text);
    return 200;
}

/*
 typedef struct {
    const char* name;       // 函数名
    const char* signature; // 函数的签名
    void*       fnPtr;     // 函数指针
 } JNINativeMethod;
 */
static const JNINativeMethod jniNativeMethod[] = {
        {"dynamicJavaMethod01", "()V",                   (void *) (dynamicMethod01)},
        {"dynamicJavaMethod02", "(Ljava/lang/String;)I", (int *) (dynamicMethod02)},
};

const char *mainClassName = "com/jni/dynamic/register/TestDemo";
// JNI JNI_OnLoad函数,如果你不写JNI_OnLoad,默认就有JNI_OnLoad,如果你写JNI_OnLoad函数 覆写默认的JNI_OnLoad函数
extern "C"
JNIEXPORT jint JNI_OnLoad(JavaVM *javaVm, void *) {


    JNIEnv *jniEnv = nullptr;
    int result = javaVm->GetEnv(reinterpret_cast(&jniEnv), JNI_VERSION_1_6);

    // result 等于0  就是成功    
    if (result != JNI_OK) {
        return -1; // 会奔溃
    }

    cout << "System.loadLibrary ---》 JNI Load init"<< endl;

    jclass mainClass = jniEnv->FindClass(mainClassName);

    // jint RegisterNatives(Class, 我们的数组==jniNativeMethod, 注册的数量 = 2)
    jniEnv->RegisterNatives(mainClass,
                            jniNativeMethod,
                            sizeof(jniNativeMethod) / sizeof(JNINativeMethod));

    cout << ("动态 注册没有毛病") << endl;

    return JNI_VERSION_1_6; //  // AS的JDK在JNI默认最高1.6      存Java的JDKJNI 1.8
}

public static void main(String[] args) {

        System.out.println("-------------------start main-------------------");

        TestDemo demo = new TestDemo();

        demo.dynamicJavaMethod01();

        System.out.println("-------------------华丽的分割线-------------------");

        demo.dynamicJavaMethod02("hello 2222");

    }

RUN>

System.loadLibrary ---》 JNI Load init
动态 注册没有毛病
-------------------start main-------------------
我是动态注册的函数 dynamicMethod01...
-------------------华丽的分割线-------------------
我是动态注册的函数 dynamicMethod02... hello 2222
image

总结

"动态注册"功能还是比较简单的,只需要搞清楚JNI_OnLoad()RegisterNatives()函数的使用就行。

Demo-GitHub

你可能感兴趣的:(JNI-静态注册和动态注册)