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 <jni.h>
/* 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 <string>
#include <iostream>

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 <jni.h>

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 <string>
#include <iostream>

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<void **>(&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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容