native 方法的动态注册

动态注册的原理

JNI 允许我们提供一个函数映射表,注册给 JVM,这样 JVM 就可以用函数映射表来调用相应的函数,而不必通过函数名来查找相关函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// java 代码中的函数是 dynamicRegFromJni,调用的 native 方法是 nativeDynamicRegFromJni,该方法没有参数, 返回值是一个字符串。
static jstring nativeDynamicRegFromJni(JNIEnv *env, jobject obj)
{
return (*env) -> NewStringUTF(env, "动态注册调用成功");
}
//函数映射表
JNINativeMethod nativeMethod[] = {{"dynamicRegFromJni", "()Ljava/lang/String;", (void*)nativeDynamicRegFromJni}};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;

jclass clz = (*env) -> FindClass(env, "com/sakura/hellojni/MainActivity");

(*env) -> RegisterNatives(env, clz, nativeMethod, sizeof(nativeMethod) / sizeof(nativeMethod[0]));

return 0;
}

函数映射表

JNINativeMethod

这是一个结构体,在 jni.h 头文件中定义:

1
2
3
4
5
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;

Java 与 jni 通过该结构体建立联系,其中有三个变量:

  • name:Java 中函数的名字。
  • signature:签名符号,描述了函数的参数和返回值
  • fnPtr:函数指针,指向一个被调用的函数

example

例如

1
JNINativeMethod nativeMethod[] = {{"dynamicRegFromJni", "()Ljava/lang/String;", (void*)nativeDynamicRegFromJni}};

可以看出,里面有一个成员,该成员第一个参数 “dynamicRegFromJni”,java 函数名;第二个参数“()Ljava/lang/String:”,是签名符号,意思是该函数没有参数,返回一个字符串 ;第三个参数就是要调用的 native 方法。
其他例子:

1
2
3
4
5
6
7
8
/*
* JNI registration.
*/
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};

JNI_OnLoad()函数

当 java 通过 System.loadLibrary 加载完 JNI 动态库后,紧接着会调用 JNI_OnLoad 的函数。

RegisterNatives

动态注册的工作就是在这里完成的。
RegisterNatives在 jni.h 中是这么定义的:

c++

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)
该函数有三个参数:
clazz: java 类名,通过 FindClass 得到
methods: JNINativeMethod 结构体指针
nMethods: 方法个数

调用:

1
(*env) -> RegisterNatives(env, clz, nativeMethod, sizeof(nativeMethod) / sizeof(nativeMethod[0]));

c

jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,jint);
该函数有四个参数:
JNIEnv*: env
clazz: java 类名,通过 FindClass 得到
methods: JNINativeMethod 结构体指针
nMethods: 方法个数

调用:

1
env -> RegisterNatives(clz, nativeMethod, sizeof(nativeMethod) / sizeof(nativeMethod[0]));

c和c++

1
2
3
4
5
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
#else
typedef const struct JNINativeInterface* JNIEnv;
#endif

这是因为在jni.h中,c++的JNIEnv是_JNIEnv,而c的是JNINativeInterface*

1
2
3
4
5
6
7
8
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
...
...
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
jint nMethods)
{ return functions->RegisterNatives(this, clazz, methods, nMethods); }

在c++中,实际上调用的是functions->RegisterNatives(this, clazz, methods, nMethods)
而functions是JNINativeInterface*类型的指针。

1
2
3
4
5
struct JNINativeInterface {
...
...
jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,jint);
}

c中,JNIEnv *env,实际上是JNINativeInterface **,这样的NINativeInterface的二级指针。
所以
(*env) -> RegisterNatives(env, clz, nativeMethod, sizeof(nativeMethod) / sizeof(nativeMethod[0]))
就是先通过解引用得到JNINativeInterface *结构体指针,再通过它调用
jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,jint);
这个函数指针来使用函数。

tip:之所以是函数指针,是因为c中结构体成员中不能有函数