Android中JNI&NDK入门(三) 之 动态注册Native函数

1 前言

前面两篇文章中,已经对JNI有了一些介绍。现在我们来回顾一下,它主要是通过使用javac -h命令来生成了一个.h的头文件,来产生Java和Native两边方法函的注册关联。这样当Java代码中去执行Native方法的时候,就会通过两边的关联的映射关系来找到这些Native真正实现的地方。事实上,JNI有两种关联Native方法的途径,分别是静态注册和动态注册。

2 注册方式

2.1 静态注册

前面文章所列举的Demo中使用命令生成.h头文件的方式就是使用了静态注册的方式。可以发现通过这种方式生成的.h头文件中,函数的名称是有规律的,如:Java_com_zyx_jnidemo_JNIUtils_getInfo,规律就是:Java前缀 + Native方法所在类的全称(用_替换.) + Native方法名。

静态注册的好处就是全自动生成操作方便,但缺点也是相当明显,就是函名过长和不够灵活。

2.2 动态注册

动态注册是通过RegisterNatives函数来将Java层和Native层的方法和函数动态关联起来,而且无需遵循特定的方法命名格式,使得代码可以更加的灵活。

当我们在Java代码中使用System.loadLibarary()方法来加载so文件时,Java虚拟机就会去调用JNI_OnLoad函数,该函数的作用是告诉虚机机应该使用哪个版本的JNI,如果我们没有指定,它默认是使用JNI1.1版本。JNI_OnLoad函数中还可以做一些初始化的事件,它对应反加载函数是JNI_OnUnload。所以我们要想进行Native函数的动态注册,最佳的时机就是在JNI_OnLoad函数中去执行。

4 继续修改Hello World

继续使用前面文章中的Demo,这次我们不通过命令生成.h头文件,而且自己创建一个新的.h头文件JNIUtils.h,内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_zyx_jnidemo_JNIUtils */

#ifndef _Included_com_zyx_jnidemo_JNIUtils
#define _Included_com_zyx_jnidemo_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     com_zyx_jnidemo_JNIUtils
 * Method:    getInfo
 * Signature: ()Ljava/lang/String;
 */
//JNIEXPORT jstring JNICALL Java_com_zyx_jnidemo_JNIUtils_getInfo
//  (JNIEnv *, jclass);


/*
 * Native层对应Java层中的方法
 */
static jstring getInfoToNative(JNIEnv * env, jclass thiz);

/*
 * Java中所定义的Native方法映射表
 */
static JNINativeMethod gJniNativeMethods[] = {
        {"getInfo", "()Ljava/lang/String;", (void*)getInfoToNative},
};

/*
 * 初始化
 */
jint JNI_OnLoad(JavaVM* vm, void* reserved);


#ifdef __cplusplus
}
#endif
#endif

修改JNIUtils.cpp文件:

#include "JNIUtils.h"
#include <stdio.h>
#include <android/log.h>

//JNIEXPORT jstring JNICALL Java_com_zyx_jnidemo_JNIUtils_getInfo(JNIEnv * env, jclass thiz) {
//    __android_log_print(ANDROID_LOG_DEBUG, "zyx", "Hello world from JNI !");
//    //return env->NewStringUTF("Hello world from JNI !");
//
//    jclass clazz = env->FindClass("com/zyx/jnidemo/JNIUtils");
//    if (clazz == NULL) {
//        return env->NewStringUTF("find class error");
//    }
//    jmethodID methodId = env->GetStaticMethodID(clazz, "getJavaInfo", "(Ljava/lang/String;I)Ljava/lang/String;");
//    if(methodId == NULL) {
//        return env->NewStringUTF("find method error");
//    }
//    jstring info = env->NewStringUTF("Hello world from JNI !");
//    jint index = 2;
//    return (jstring)env->CallStaticObjectMethod(clazz, methodId, info, index);
//}

static jstring getInfoToNative(JNIEnv * env, jclass thiz) {
    return env->NewStringUTF("Hello world from JNI !3");
}

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

    JNIEnv* jniEnv = NULL;

    // 从虚拟机中获得JNIEnv,同时指定jni版本
    if (vm->GetEnv((void**) &jniEnv, JNI_VERSION_1_6) != JNI_OK || jniEnv == NULL) {
        return JNI_ERR;
    }

    // 获得Java代码中Natives方法所在类
    jclass clazz = (jniEnv)->FindClass("com/zyx/jnidemo/JNIUtils");
    if (clazz == NULL) {
        return JNI_ERR;
    }

    // 执行注册
    jint nMethods = sizeof(gJniNativeMethods) / sizeof(JNINativeMethod);
    if ((jniEnv)->RegisterNatives(clazz, gJniNativeMethods, nMethods) < 0) {
        return JNI_ERR;
    }

    return JNI_VERSION_1_6;
}

也是非常简单的修改,然后再重新编译程序,运行可见:

说明:

JNIUtils.h中定义了两个函数和一个数组:

getInfoToNative                               Native层对应Java层中要注册关联的函数

JNI_OnLoad                                     进行初始化和指定JNI版本

gJniNativeMethods                          是一个JNINativeMethod类型的数组,它是一个函数是映射表,用于关联Java层中定义的Native方法和Native层的相应函数,其中第一个参数是Java中的方法名,第二个参数是方法的签名,第三个参数是Native中函数的指针

JNIUtils.cpp中对两个定义的函数进行了实现,可见在JNI_OnLoad函数内部使用了RegisterNatives函数传入Java中的类和gJniNativeMethods数组来执行动态注册关联两边的方法函数。

 

点击下载示例

 

发布了106 篇原创文章 · 获赞 37 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/lyz_zyx/article/details/88690930