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数组来执行动态注册关联两边的方法函数。