之前的例子当Java层调用本地函数的时候,他会从我们加载的库中寻找固定格式与本地函数名字对应的函数,如果没有,就会报错。如果找到,就会为这个本地方法与本地函数建立一个关联关系,其实就是保存JNI层函数的函数指针,以后再调用本地方法时,直接调用这个函数指针就可以了,当然这项工作是由虚拟机完成的,
但是这种方式在实际中用的并不多,首先这种静态注册方法,必须要用javah命令生成一个头文件,其次,本地函数名太长,书写不方便,并且在初次调用本地函数时,要根据函数名字搜索对应的JNI层函数来建立关联关系,这样会影响运行效率。
在应用中,会用一种动态注册的方法,来保存本地方法与本地函数的一一对应关系,是一个叫JNINativeMethod的结构,其定义如下
typedef struct{
//Java 中native函数的名字,不用携带包的路径,例如“native_init”
const char * name;
//Java函数的签名信息,用字符串表示,是参数类型和返回值类型的组合
const char * signature;
//JNI层对应函数的函数指针,注意其实void * 类型
void * fnPtr;
}JNINativeMethod;
我们按照从注册到实现的流程简单的过一下,看MediaScanner JNI层是如何做的,
动态注册这些信息是当Java层通过System.loadLibrary加载完JNI动态库后,紧接着会查找该库中一个叫JNI_Onload的函数。如果有,就会调用它,而动态注册的工作就是在这里完成的
在libmedia_jni.so的JNI_Onload函数是在android_media_MediaPlayer.cpp中实现的
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
//该函数的第一个参数类型为JavaVM,这是虚拟机在JNI层的代表,每一个Java进程只有一个这样的JavaVM
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
if (register_android_media_MediaPlayer(env) < 0) {
LOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
}
if (register_android_media_MediaRecorder(env) < 0) {
LOGE("ERROR: MediaRecorder native registration failed\n");
goto bail;
}
//动态注册MediaScanner的JNI函数
if (register_android_media_MediaScanner(env) < 0) {
LOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}
if (register_android_media_MediaMetadataRetriever(env) < 0) {
LOGE("ERROR: MediaMetadataRetriever native registration failed\n");
goto bail;
}
if (register_android_media_AmrInputStream(env) < 0) {
LOGE("ERROR: AmrInputStream native registration failed\n");
goto bail;
}
if (register_android_media_ResampleInputStream(env) < 0) {
LOGE("ERROR: ResampleInputStream native registration failed\n");
goto bail;
}
if (register_android_media_MediaProfiles(env) < 0) {
LOGE("ERROR: MediaProfiles native registration failed");
goto bail;
}
/* success -- return valid version number */
//必须返回这个值 ,否则报错
result = JNI_VERSION_1_4;
bail:
return result;
}
只拿动态注册MediaScanner的JNI函数来说明,其调用register_android_media_MediaScanner(env)。具体实现位置在
android_media_MediaScanner.cpp
......................
static JNINativeMethod gMethods[] = {
{"processDirectory", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", (void*)android_media_MediaScanner_processDirectory},
{"processFile", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", (void *)android_media_MediaScanner_processFile},
{"setLocale", "(Ljava/lang/String;)V", (void *)android_media_MediaScanner_setLocale},
{"extractAlbumArt", "(Ljava/io/FileDescriptor;)[B", (void *)android_media_MediaScanner_extractAlbumArt},
{"native_init", "()V", (void *)android_media_MediaScanner_native_init},
{"native_setup", "()V", (void *)android_media_MediaScanner_native_setup},
{"native_finalize", "()V", (void *)android_media_MediaScanner_native_finalize},
};
...................
//注册JNINativeMethod函数
int register_android_media_MediaScanner(JNIEnv *env)
{
//调用AndroidRuntime的registerNativeMethods函数,第二个参数表明是Java中的哪个类
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaScanner", gMethods, NELEM(gMethods));
}
在AndroidRuntime.cpp中的具体实现如下
int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
在往后就是调用JNIHelp.c中的 jniRegisterNativeMethods函数
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
LOGV("Registering %s natives\n", className);
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'\n", className);
return -1;
}
int result = 0;
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
LOGE("RegisterNatives failed for '%s'\n", className);
result = -1;
}
(*env)->DeleteLocalRef(env, clazz);
return result;
}
看起来很复杂 其实动态注册的工作值用两部就可以完成,只不过调转的文件比较多
clazz = (*env)->FindClass(env, className);
(*env)->RegisterNatives(env, clazz, gMethods, numMethods)
补充签名机制原因
Java支持重载,也就是说,可以定义同名但是不同参数的函数,但仅仅根据函数名是没法找到具体函数的。为了解决这个问题,JNI技术中就将参数类型和返回值信息结合在一起作为一个函数的签名信息,有了签名信息和函数名,就能很顺利的找到Java中的函数了