在JNI使用详解(一)中我们有提到,native_init函数对应JNI函数是android_media_MediaScanner_native_init,但是是如何对应的主要还是根据包名来对应的,native_init函数的全路径android.meida.MediaScanner.native_init,对应的JNI层函数的名字是android_media_MediaScanner_native_init,在Native语言中,符号“.”有着特殊的意义,所以JNI层需要把Java函数名称(包括包名)中的“.”换成“_”.也就是通过这种方式,native_init找到了自己JNI层的android.media.MediaScanner.native_init.注册本质就是将Java层的native函数和JNI层对应的实现函数关联,有了这种关联,调用Java层的native函数,就能顺利转到JNI层对应的函数执行。
注册方法一般有两种,一种方法是静态方法,需要用到Java工具程序javah,用这个程序根据编译生成的.class文件生成一个JNI层的.h头文件,在这个头文件中声明了JNI层函数,只要创建一个Native文件去实现这个声明就可以了,MediaScanner对应的JNI层头文件是android_media_MediaScanner.h.下面看这种方式生成的头文件:
android_media_MediaScanner.h
#include<jni.h>
#ifndef _Included_android_media_MediaScanner
#define _Included_anrdoid_media_MediaScanner
#ifdef _cplusplus
extern "C" {
#endif
....
//processFile的JNI函数
JNIEXPORT void JNICALL Java_android_media_Mediascanner_processFile
...
//native_init对应的JNI函数
JNIEXPORT void JNICALL Java_android_media_MediaScanner_native_linit(JNIEnv *, jclass);
#ifdef _cplusplus
}
#endif
#endif
native_init和processFile的JNI层函数被声明成,Java层调用native_init函数时,会从对应的JNI库中寻找Java_android_media_MediaScanner_native_linit函数,如果没有,就会报错,如果找到,就会为这个native_init和Java_android_media_MediaScanner_native_linit建立一个关联关系,这种关系其实就是由虚拟机去保存JNI层的函数指针,以后调用native_init函数时,直接使用这个函数指针就可以了。静态注册弊端一是说有声明了native函数的java类都要经过生成class文件来产生native的头文件,二是根据搜索函数名来对应JNI层函数建立关联关系,影响效率
JNIEXPORT void JNICALL Java_android_media_MediaScanner_native_linit(JNIEnv *, jclass);
JNIEXPORT void JNICALL Java_android_media_Mediascanner_processFile;
动态注册能够很好的解决静态注册的这两个弊端,静态注册通过搜索函数名来关联,最终目的还是保存JNI层的函数指针,动态注册直接保存JNI层的函数指针就可以了,这样就不需要javah工具,不需要首次关联耗时去搜索,在JNI技术中,用来保存这种一一对应关系的是一个叫JNINativeMethod的结构,定义如下:
typedef struct {
//Java中native函数的名字,不用携带包的路径
const char* name;
//Java函数的签名信息,用字符串表示,是参数类型和返回值类型的组合。
const char* signature;
//JNI层对应函数的函数指针
void* fnPtr;
}JNINativeMethod;
//android_media_MediaScanner.cpp
//定义一个JNINativeMethod数组,其成员就是MS中所有native函数的一一对应关系
static JNINativeMethod GMethods[] = {
....
{
"processFile" //Java中native函数的函数名。
//processFile的签名信息。
"(Ljava/lang/String;Ljava/lang/String;Landorid/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函数
return AndroidRuntime::registerNativeMethods(env,"android/media/MediaScanner",gMethods,NELEM(gMethods));
}
//AndroidRunTime类提供了一个registerNativeMethods函数来完成注册工作
//AndroidRunTime.cpp
int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,const JNINativeMethod* gMethods, int numMethods)
{
//调用jniRegisterNativeMethods函数完成注册
return jniRegisterNativeMethods(env,className,gMethods,numMethods);
}
//JNIHelp.c
int jniRegisterNativeMethods(JNIEnv* env,const char* className,const JNINativeMethod* gMethods, int numMethods)
{
jclass class;
clazz = (*env)->FindClass(env,className);
...
//实际上是调用jNIEnv的RegisterNative函数完成注册的
if (*env)->Registernatives(env,clazz,gMethods,numMethods)<0) {
return -1;
}
return 0;
}
//总结下env指向一个JNIEnv结构体,classname为对应的Java类名,由于JNINativeMethod中使用的函数名并非全路径名,所以要指明是哪个类
jclass clazz = (*env)->FindClass(env,className);
//调用JNIEnv的RegisterNativ函数,注册关联关系。。
(*env)->RegisterNatives(env,clazz,gMethods,numMethods);
其中jniRegisterNativeMethods是Android平台中为了方便JNI使用而提供的一个帮助函数,在自己的JNI层代码中使用这种方法,就可以完成动态注册了。当Java层通过System.loadLibrary加载JNI动态库后,紧接着会调用JNI_Onload函数,动态注册就是在这里完成的,所以要实现JNI_Onload函数,才能实现动态注册,libmedia_jni.so的JNI_OnLoad函数理论上可以任意JNI层实现,由于Media相关类大量使用JNI,所以一般都放到android_media_MediaPlayer.cpp.
//android_media_MediaPlayer.cpp
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
//JavaVM 类型代表虚拟机,每个进程只有一个vm
JNIEnv* env = NULL;
jint result = -1;
if(vm->GetEnv((void **) &env, JNI_VERSION_1_4)! = JNI_OK) {
goto bail;
}
.... //动态注册MediaScanner的JNI函数。
if (register_android_media_MediaScanner(env) < 0) {
goto bail;
}
....
return JNI_VERSION_1_4;//必须返回这个值,否则会报错(原因不详)
JNI函数注册的相关内容介绍完了。