Android NDK学习(五):Java调用Native代码流程总结

一、概述

当 Java 调用 native 方法时,虚拟机是怎么知道该调用 so 中的哪个方法呢?这就需要用到注册了,通过注册,将指定的 native 方法和 so 中对应的方法绑定起来(函数映射表),这样就能够找到相应的方法了。

native 方法的注册方式分为静态注册 和 动态注册 两种。默认的实现方式即静态注册。

二、静态注册

静态注册通过 JNIEXPORT 和 JNICALL 两个宏定义声明方法,命名规则为:Java + 包名 + 类名 + 方法名, 其中使用下划线将每部分隔开,包名也使用下划线隔开,如果名称中本来就包含下划线,将使用下划线加数字替换。在虚拟机加载 so 时发现上面两个宏定义的函数时就会链接到对应的 native 方法。

例如:

// Java native method
public native String stringFromJNI();
// JNI method 
JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_stringFromJNI( JNIEnv *env, jobject instance);

优点

实现简单,在Android Studio上,能通过快捷键自动生成native方法。

缺点:

命名必须遵循注册规则,且存在名字过长的缺点。

三、动态注册

通过 RegisterNatives 方法手动完成 native 方法和 so 中的方法的绑定,这样虚拟机就可以通过这个函数映射表直接找到相应的方法了。

这里不得不提到一个方法:

extern "C"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    jint result = -1;
    javaVm = vm; // 赋值全局jvm
    JNIEnv *env;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return result;
    }
    return JNI_VERSION_1_6;
}

 

Java调用System.loadLibrary()加载一个库的时候,会首先在库中搜索JNI_OnLoad()函数,如果该函数存在,则执行它;

JNI_OnLoad()的作用主要有几点:

  1. 告诉JVM,这个库需要要求使用的JNI版本是什么 
  2. 执行初始化操作
  3. 将JavaVM参数保存为全局对象,方便以后在任何地方获取JNIEnv对象

 

注意: JNI_OnLoad方法在每一个库中只能存在一个。 如果使用动态注册的方式进行native方法的注册,可以在此方法中进行操作。

如果使用动态注册就不需要再使用 JNIEXPORT 和 JNICALL 两个宏定义声明指定的方法,也不用依照固定的命名规则命名方法,而是通过了一个 RegisterNatives 方法完成了动态注册。

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

方法参数:

  • clazz:指定的类,即 native 方法所属的类
  • methods:方法数组,这里需要了解一下 JNINativeMethod 结构体
  • nMethods:方法数组的长度

成功则返回 JNI_OK (0),失败则返回一个负值。

这里在说明一下JNINativeMethod的结构体构成:

typedef struct {
    const char* name; // native 的方法名
    const char* signature; // 方法签名,例如 ()Ljava/lang/String;
    void*       fnPtr; // 函数指针
} JNINativeMethod;

优点:

1. 方便绑定的切换,重构代码时更为简单

2. 调用时无需再进行查找,对Native方法的函数名无要求

缺点:

1. 会导致无法通过Java Native方法自动关联跳转,需要自行查找。

 


 

posted @ 2018-06-08 14:02  灰色飘零  阅读(676)  评论(0编辑  收藏  举报