JNI是Java Native Interface的缩写,中文为Java本地调用
JNI可以实现两种功能:
(1)java程序中的函数可以调用native的函数(一般是C/C++编写的函数)
(2)Native的函数可以调用java层的函数
下面以MediaScanner为例,大概说明JNI的原理
上图看出,对于JNI层的命名方式:lib模块名_jni.so(Native层库名_jni.so)。JNI层必须实现为动态库的形式,这样Java虚拟机才能加载,JNI本身用native语言来实现。
加载JNI库是在类的静态块中完成的,静态块是在类加载时运行的,可以简单理解为在new一个对象之前就已经执行了。加载这个库的原因是可以在Java层通过库可以调用到Native层。
究竟native_init()函数会调用native层的什么函数呢?以下注册是JNI注册流程图
所以,要实现native层和Java层的关联,就要实现JNI_OnLoad和register_android_media_MediaScanner函数,如下图
JNI_OnLoad函数在android_media_MediaPlayer.cpp中实现
register_android_media_MediaScanner函数在android_media_MediaScanner.cpp中实现
其中参数gMethods的结构如下
JNINativeMethod结构就把java层的方法和native层的方法关联起来了,第一个参数代表java层的方法名,第三个参数代表native层的方法名,第二个参数是函数的签名信息,由参数类型和返回值类型组成。以ProcessFile举例说明:
private native void processFile(String path, String mimeType, MediaScannerClient client); "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V“
由此可见,签名信息包含了native层方法的参数信息和返回值信息,括号内是参数类型的标识,引用类型的格式是“L包名”,包名中的”.“换成”/“,最右边的是返回值类型的标识,V代表void
签名信息只用于找到函数,因为java有重载,不用于数据传递
上图可看出,java层的数据类型在native层一一对应着一种数据类型,数据类型的转换,分为基本数据类型和引用数据类型。
基本数据类型转换
引用数据类型转换
native层如何获取到java层获取的数据?
JNIEnv是一个与线程相关的代表JNI环境的结构体,JNIEnv提供了一些函数操作Java层传下来的数据,如:
const char *mimeTypeStr = env->GetStringUTFChars(mimeType, NULL);
native层如何获取Java层传下来的对象?
对象无非由成员函数和成员变量组成,操作Java层传下来的对象,本质就是操作这些成员函数和成员变量,JNI中分别用jfieldID和jmethodID来表示java对象的成员变量和函数
jfieldID和jmethodID可通过JNIEnv得到,如
jmethodID mScanFileMethodID; mScanFileMethodID = env->GetMethodID( mediaScannerClientInterface, "scanFile", "(Ljava/lang/String;JJZZ)V"); mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize, isDirectory, noMedia);
通过上述代码,就可以调用java层的scanFile函数了
最后,各位看官辛苦了