一、JNI(Java Native Interface, Java本地调用)
作用:在Java程序中的函数可以调用C/C++编写的函数;
在C/C++程序中的函数可以调用Java编写的函数;
JNI实例
Java(MediaScanner)<——>JNI(libmedia_jni.so)<——>Native(libmedia.so)
在Java层中的MediaScanner类中有一些函数需要由Native层来实现;
MediaScanner将通过JNI库libmedia_jni.so和Native层的libmedia.so交互;
从上面的分析可知,JNI层必须实现为动态库的形式,这样Java虚拟机才能加载和调用 它的函数;
Java层的MediaScanner分析
MediaScanner.java
public class MediaScanner
{
static {
/*1)加载对应的JNI库,media_jni是JNI库的名字,在实际加载动态库的时候将其拓展成libmedia_jni.so,windows平台上拓展为media_jni.dll*/
System.loadLibrary("media_jni");
native_init(); //调用native_init函数
}
......
//2)声明一个native函数,native为Java关键字,表示它将由JNI层完成。
private static native final void native_init();
......
private native void processFile(String path, String mimeType, MediaScannerClient client);
......
}
上面的代码列出了两个要点:一个是加载JNI库,另一个是Java的native函数。
加载JNI库
原则上在调用native函数前,都可以加载;通常在类的static语句中加载;
调用System.loadLibrary()方法;
从上面的代码可以发现,native_init和processFile函数前都有Java的关键字native,它表示这两个函数将由JNI层来实现;
JNI层MediaScanner的分析
MediaScanner的JNI层代码在android_media_MediaScanner.cpp中
android_media_MediaScanner.cpp
//native_init的JNI实现
static void android_media_MediaScanner_native_init(JNIEnv *env)
{
......
}
//processFile的JNI层实现
static void android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client)
{
......
}
Java层native_init函数如何对应JNI层的android_media_MediaScanner_native_init函数?
注册JNI函数
native_init函数位于android.media这个包中,它的全路径名应该是android.media.MediaScanner.native_init,而JNI层函数的名字是android_media_MediaScanner_native_init;
JNI层中把Java函数名称中的“.”换成“_”,通过这种方式native_init找到JNI层的对应函数android_media_MediaScanner_native_init;
1、静态方法
使用Java的工具程序javah找对应的JNI函数,流程如下:
先编写Java代码,然后编译生成.class文件。
使用Java工具程序javah,$ javah -o output packagename.classname
之后生成output.h的JNI层头文件,其中packagename.classname是Java代码编译后的class文件,在output.h中声明了对应的JNI层函数,只要实现里面的函数即可。
如MediaScanner对应JNI层头文件就是android_media_MediaScanner.h
对应关系:
当Java层调用native_init函数时,它会从对应的JNI库中寻找Java_android_media_MediaScanner_native_init函数,如果没有就报错。如果找到就为这两个函数建立一个关联关系,就是保存JNI层函数的函数指针,以后再调用就可以使用这个函数指针了,这些工作是由虚拟机完成的。
2、动态注册
JNI中使用JNINativeMethod结构保存上面Java和JNI层函数的一一对应的关系;
//定义一个JNINativeMethod数组,其成员就是Java层中所有的native函数的一一对应关系
static JNINativeMethod gMethods[] = {
......
{
"processFile" //Java中native函数的函数名
//processFile的签名信息,签名信息由函数参数及返回值构成;
"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
(void *)android_media_MediaScanner_processFile //JNI层对应的函数指针
},
......
{
"native_init",
"()V",
(void *)android_media_MediaScanner_native_init
},
};
//注册JNINativeMethod数组
int register_android_media_MediaScanner(JNIEnv *env)
{
//调用AndroidRuntime的registerNativeMethods函数,第二个参数表明是Java中的哪个类
return AndroidRuntime::registerNativeMethods(env, "andorid/media/MediaScanner", gMethods, NELEM(gMethods));
}
AndroidRunTime提供了一个registerNativeMethods函数来完成注册工作,下面看registerNativeMethods的实现,代码如下:
int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethod, int numMethods)
{
//调用jniRegisterNativeMethods函数完成注册
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
其中jniRegisterNativeMethods是Android平台为了方便JNI使用而提供的一个帮助函数,代码如下:
int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
......
//实际上是调用JNIEnv的RegisterNatives函数完成注册的
if((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return -1;
}
return 0;
}
动态注册的工作只用两个函数就能完成。
jclass clazz = (*env)->FindClass(env, className);
(*env)->RegisterNatives(env, clazz, gMethods, numMethods);
动态注册的函数,当Java层通过System.loadLibrary加载完JNI动态库后,紧接着会查找该库中一个叫JNI_OnLoad的函数,调用它完成注册;
建议实现这个JNI_OnLoad函数,在其中做一些初始化的工作;
函数签名信息的生成
java提供了一个javap的工具可以帮助生成函数和变量的签名信息;
$ java -s -p xxx
其中xxx为编译生成的class文件,s表示输出内部数据类型的签名信息,p表示打印所有函数和成员的签名信息,默认只会答应public的成员和函数的签名信息;
二、XPCOM组件