第一个参数:JNIEnv* 是定义任意 native 函数的第一个参数(包括调用 JNI 的 RegisterNatives 函数注册的函数),指向 JVM 函数表的指针,函数表中的每一个入口指向一个 JNI 函数,每个函数用于访问 JVM 中特定的数据结构。
第二个参数:调用 Java 中 native 方法的实例或 Class 对象,如果这个 native 方法是实例方法,则该参数是 jobject,如果是静态方法,则是 jclass。
第三个参数:Java 对应 JNI 中的数据类型,Java 中 String 类型对应 JNI 的 jstring 类型。(后面会详细介绍 JAVA 与 JNI 数据类型的映射关系)。
函数返回值类型:夹在 JNIEXPORT 和 JNICALL 宏中间的 jstring,表示函数的返回值类型,对应 Java 的String 类型。
如果你需要装逼的话你就可以自己去写.h文件,然后就可以抛弃javah -jni 命令,只需要按照函数命名规则编写相应的函数原型和实现即可(逼就是这么装出来的)
RegisterNatives动态获取本地方法是不是感觉一个方法的名字太长非常的蛋疼,然后我们呢直接使用,RegisterNatives来自己命名调用native方法,这样是不是感觉好多了。
要实现呢,我们必须重写JNI_OnLoad()方法这样就会当调用 System.loadLibrary(“XXXX”)方法的时候直接来调用JNI_OnLoad(),这样就达到了动态注册实现native方法的作用。
/* * System.loadLibrary()时调用 * 如果成功返回JNI版本, 失败返回-1 */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } assert(env != NULL); if (!registerNatives(env)) {//注册 return -1; } //成功 result = JNI_VERSION_1_4; return result; }并且我们需要为类注册本地方法,那样就能方便我们去调用,不多说看方法:
static int registerNatives(JNIEnv* env) { return registerNativeMethods(env, JNIREG_CLASS, gMethods,sizeof(gMethods) / sizeof(gMethods[0])); }也可以为某一个类注册本地方法
static int registerNativeMethods(JNIEnv* env , const char* className , JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { return JNI_FALSE; } if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; }JNINativeMethod 结构体的官方定义
typedef struct { const char* name; const char* signature; void* fnPtr; } JNINativeMethod;第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了Java中函数的参数和返回值
第三个变量fnPtr是函数指针,指向native函数。前面都要接 (void *)
第一个变量与第三个变量是对应的,一个是java层方法名,对应着第三个参数的native方法名字(不明白请看后面代码就会清楚了)。
哈哈最后我们就把native方法绑定到JNINativeMethod上我们来看下事例:
static JNINativeMethod gMethods[] = { {, , (void *)com_media_ffmpeg_FFMpegPlayer_setDataSource}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_setVideoSurface}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_prepare}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_start}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_stop}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_getVideoWidth}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_getVideoHeight}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_seekTo}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_pause}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_isPlaying}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_getCurrentPosition}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_getDuration}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_release}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_reset}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_setAudioStreamType}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_native_init}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_native_setup}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_native_finalize}, {, , (void *)com_media_ffmpeg_FFMpegPlayer_native_suspend_resume}, };第一个参数就是我们写的方法,第三个就是.h文件里面的方法,第二个参数显得有点难度,这里会主要去讲。
主要是第二个参数比较复杂:
括号里面表示参数的类型,括号后面表示返回值。
“()” 中的字符表示参数,后面的则代表返回值。例如”()V” 就表示void * Fun();
“(II)V” 表示 void Fun(int a, int b);
“(II)I” 表示 int sum(int a, int b);
这些字符与函数的参数类型的映射表如下:
字符 Java类型 C类型
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
数组则以”[“开始,用两个字符表示