Jni学习之Java调用C++的代码
[TOC]
概述
当执行一个 Java 的 native 方法时,虚拟机是怎么知道该调用 so 中的哪个方法呢?
我们肯定是要简历一个上层Java和底层C++的一个方法调用关系的映射。那么这个映射关系就是JNI开发中的方法注册。通过方法注册将指定的 native 方法和 so 中对应的方法绑定起来(函数映射表),这样就能够找到相应的方法了。
JNI用法
为了使用JNI,在调用本地方法前必须把C/C++代码所在的动态库装载到进程的内存空间中。装载库文件调用的是System类的LoadLibrary()方法,原型如下:
1 | public static void loadLibrary(String libName) |
loadLibrary()方法的参数是动态库文件名称的一部分。Android JNI动态库的名称必须以“lib”开头,这里传入的参数是去掉前缀“lib”,以及后缀“.so”的中间部分。例如,库文件名是libhello-jni.so,装载库的语句要写成loadLibrary(“hello-jni “)。这里为什么不使用全文件名呢?JNI原本是Java的产物,Java希望代码能跨平台使用,不同平台动态库的后缀并不一样,例如,Linux下是.so, windwos下是.dll。因此,为了适应不同的平台,这里传入的参数去掉了和系统相关的部分。
1 | /** |
调用loadLibrary()方法不需要指定库文件所在的路径,Android会在几个系统目录下查找动态库。
为了保证调用native方法前所需要的动态库已经加载,loadLibrary()的调用位置一般是放在类的static块中,这样进程初始化时就能执行装载语句了。
JNI开发注册分为 静态注册 和 动态注册 两种。默认的实现方式即静态注册。
方法注册
静态注册
定义
通过 JNIEXPORT 和 JNICALL 两个宏定义声明,在虚拟机加载 so 时发现上面两个宏定义的函数时就会链接到对应的 native 方法。
1 | /** |
我们在上层Java的文件中创建一个Native的方法声明。
1 | /** |
我们可以让Android Studio来自动帮我们生成一个stringFromJNIJni里面的方法
1 | // ==============================Java调用C++的方法================================================= |
Java虚拟机调用 Native 方法,会传入两个对象。分别是JNIEnv的指针对象。还有一个是jobject对象。这两个对象我们后面再讲。
我们先说一下,Android Studio 帮我们生成这个静态注册的Native层的JNI方法。这个方法遵循一个的命名规则。
Java_包名_类名_方法名
其中使用下划线将每部分隔开,包名也使用下划线隔开,如果名称中本来就包含下划线,将使用下划线加数字替换。
优点
- 简单明了
缺点
- 必须遵循注册规则
- 名字过长
- 运行时去找效率不高
动态注册
定义
通过 RegisterNatives 方法手动完成 native 方法和 so 中的方法的绑定,这样虚拟机就可以通过这个函数映射表直接找到相应的方法了。
还是上面的方法。
1 | /** |
JNI_OnLoad 方法
1 | JNIEXPORT int JNI_OnLoad(JavaVM *vm, void *revered) { |
我们看到代码第10行。调用 registerNativeMethods 的方法进行方法映射表的注册,就是把我们的方法注册到系统中去。
registerNativeMethods方法
定义: jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
参数:
- clazz:指定的类,即 native 方法所属的类
- methods:方法数组,这里需要了解一下 JNINativeMethod 结构体
- nMethods:方法数组的长度
1 | // 定义JNI的Class的Path的字符串宏.后续我们再看看这个字符串宏是怎么生成的?? |
JNINativeMethod:
1 | typedef struct { |
返回值:成功则返回 JNI_OK (0),失败则返回一个负值。