JNI层动态注册Native方法

Android中,通常创建native的方法是在java层写一个native的方法声明,然后通过Alt+enter进行自动生成jni的方法,或者在命令行中使用javah生成头问题然后对照头问题的函数名,在c文件中创建函数。这种属于静态注册方法,虽然在AS中使用非常方法,但是有一下几个弊端:

  1. 在调用该方法时,有额外的查找方法的性能开销
  2. 在后期需要重构代码时,jni中的函数名需要手动修改,比较麻烦
  3. 在程序运行时,无法动态更换

所以,在NDK开发时,可以使用动态注册Native方法来规避上面的问题。

动态注册方法是通过jni中的JNI_OnLoad方法实现的:

1
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);

JNI_OnLoad()方法在虚拟机加载C库是被执行(当System.loadLibrary("native-lib")时)。

JNI_OnLoad()中使用

1
2
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,
jint nMethods)

对native方法进行注册。RegisterNatives()中需要三个参数:

  • clazz指的是native方法所在的类,可以使用env->FindClass()获取;

    1
    jclass clazz = env->FindClass("com/example/jni/MainActivity");
  • methods指的是需要动态注册的方法(这是一个指向数组的指针),JNINativeMethod是一个结构体:

    1
    2
    3
    4
    5
    typedef struct {
    const char* name;
    const char* signature;
    void* fnPtr;
    } JNINativeMethod;
    • name是java层的方法名;
    • signature是方法的出参入参的类型
    • fnPtr是jni层的函数指针
  • nMethods指的是需要动态注册的方法的个数,就是methods的长度:sizeof(g_methods) / sizeof(g_methods[0])


最终的实现:

Java层:

1
public native String _test();

jni层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
extern "C" JNIEXPORT jstring JNICALL
my_jni_test(JNIEnv *env, jobject instance) {
return env->NewStringUTF("this is a method");
}

static JNINativeMethod g_methods[] = {
{
"_test",
"()Ljava/lang/String;",
(void *) my_jni_test
},
};

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
vm->GetEnv((void **) &env, JNI_VERSION_1_6);

jclass clazz = env->FindClass("com/example/jni/MainActivity");

env->RegisterNatives(clazz, g_methods, sizeof(g_methods) / sizeof(g_methods[0]));
return JNI_VERSION_1_6;
}