一:概述
JNI:Java Native Interface。
作用:连接Java世界和Native世界。Java程序中函数可以调用Native语言写的函数;Native程序中的函数可以调用Java层的函数。
二:实例:MediaScanner
2.1 关系:
Java层(MediaScanner)<---------->JNI层(libmedia_jni.so)<--------->Native层(libmedia.so)
JNI库名规则:lib模块名_jni.so 如上:media_jni就是库名
JNI 层必须实现为动态库的形式。
MediaScanner类中有一些函数需要由Native层来实现。
2.2 Java层MediaScanner分析
打开MediaScanner.java源码,\frameworks\base\media\java\android\media
<pre name="code" class="java">package android.media;
…… public class MediaScanner { static { System.loadLibrary("media_jni"); //加载media_jin库,加载时会拓展成libmedia_jni.so(linux平台)或libmedia_jni.dll(windows平台) native_init(); } ……//一些非native方法的定义 public void scanDirectories(String[] directories, String volumeName) {//非native函数 ……
}
……//一些native方法的声明 private native void processDirectory(String path, MediaScannerClient client); private native void processFile(String path, String mimeType, MediaScannerClient client); public native void setLocale(String locale); public native void setMediaflag(int mediaflag); public native byte[] extractAlbumArt(FileDescriptor fd); private static native final void native_init(); private native final void native_setup(); private native final void native_finalize(); …… }上面代码中的native_init、processDirectory等这些函数由JNI层实现。
其中native_init() 的全路径是:android.media.MediaScanner.native_init;
processDirectory 函数的全路径是:android.media.MediaScanner.processDirectory
将“.”换成“_"就是JNI对应函数的名字。
2.3 JNI层MediaScanner分析
打开源码android_media_MediaScanner.cpp,frameworks\base\media\jni
#include "jni.h" #include "JNIHelp.h"
// This function gets a field ID, which in turn causes class initialization. // It is called from a static block in MediaScanner, which won't run until the // first time an instance of this class is used. static void android_media_MediaScanner_native_init(JNIEnv *env) { ALOGV("native_init"); jclass clazz = env->FindClass(kClassMediaScanner); if (clazz == NULL) { return; } fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); if (fields.context == NULL) { return; } }
static void android_media_MediaScanner_processDirectory( JNIEnv* env, jobject thiz, jstring path, jobject client){……}需要注册JNI函数,在java层调用native函数时,才能顺利转到JNI层对应函数执行。
注册有两种方法:
1:静态注册:根据文件名确立Java函数和JNI函数的关联关系。再通过函数指针操作。有弊端:效率低,JNI函数名长……
2:动态注册:利用结构体JNINativeMethod保存某个Java native函数和对应的JNI 函数的关联关系。
多对关联关系通过JNINativeMethod类型数组来保存。
下面详细介绍动态注册:
注册过程的实现:
1:JNINativeMethod结构体
打开jni.h ,libnativehelper\include\nativehelper
typedef struct { const char* name; //Java中函数的名字 const char* signature; //签名信息即用字符串描述的函数的参数和返回值 void* fnPtr; //指向C函数的函数指针 } JNINativeMethod;2:构建JNINativeMethod类型数组来表示多对Java native函数和对应的JNI 函数的关联关系。
打开源码android_media_MediaScanner.cpp,frameworks\base\media\jni
static JNINativeMethod gMethods[] = { { "processDirectory", "(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 }, { "setMediaflag", "(I)V", (void *)android_media_MediaScanner_setMediaflag }, { "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 }, };上述数组成员与MediaScanner.java中声明的native方法以及android_media_MediaScanner.cpp中的方法是一一对应的。
观察数组成员中的签名信息,如
"(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
签名信息:由参数类型和返回值共同组成。(因为java支持函数重载,所以必须借助返回值类型和参数类型来定位函数)
对应于Java端的数据类型,我们也可以看一下下面的表:
Java 类型 | 类型签名 |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
类 | L全限定名;,比如String, 其签名为Ljava/lang/util/String; |
数组 | [类型签名, 比如 [B |
3:注册上述数组
打开源码android_media_MediaScanner.cpp,frameworks\base\media\jni
// This function only registers the native methods, and is called from // JNI_OnLoad in android_media_MediaPlayer.cpp int register_android_media_MediaScanner(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, kClassMediaScanner, gMethods, NELEM(gMethods));//传入被注册数组 }通过调用registerNativerMethods函数完成注册。registerNativerMethods函数是在AndroidRunTime.cpp中定义的,AndroidRunTime.cpp也是位于JNI层的。
打开AndroidRunTime.cpp ,路径frameworks\base\core\jni
/* * Register native methods using JNI. */ /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { return jniRegisterNativeMethods(env, className, gMethods, numMethods); }
jniRegisterNativeMethods再通过调用jniRegisterNativerMethods函数完成注册,jniRegisterNativerMethods函数是Android平台提供的帮助函数。
打开JNIHelp.cpp ,路径:\libnativehelper
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env,const char* className, const JNINativeMethod* gMethods, int numMethods) { JNIEnv*e = reinterpret_cast<JNIEnv*>(env); ALOGV("Registering %s's %d native methods...", className, numMethods); scoped_local_ref<jclass> c(env, findClass(env, className)); if (c.get() == NULL) { char* msg; asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className); e->FatalError(msg); } if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { char* msg; asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className); e->FatalError(msg); } return 0; }再通过JNIEvn的RegisterNatives函数完成注册。JNIEvn是一个重要的结构体。
总结:
JNINativeMathod中使用的是函数名而不是全名,所以需要指明是哪个类。最终是调用JNIEvn的RegisterNatives函数完成注册。
注册的时机:
当Java层调用System.loadLibrrary加载完JNI动态库后,会查找该库中的JNI_OnLoad函数,如果有则调用,完成注册。
下面看JNI_OnLoad函数的实现:
打开源码android_media_MediaPlayer.cpp,frameworks\base\media\jni
jint JNI_OnLoad(JavaVM* vm, void* reserved)//JavaVM是虚拟机在JNI层的代表 { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { ALOGE("ERROR: GetEnv failed\n"); goto bail; } assert(env != NULL); …… if (register_android_media_MediaScanner(env)< 0) { //动态注册MediaScanner的JNI函数 ALOGE("ERROR: MediaScanner native registration failed\n"); goto bail; } if (register_android_media_MediaMetadataRetriever(env) < 0) { ALOGE("ERROR: MediaMetadataRetriever native registration failed\n"); goto bail; } …… /* success -- return valid version number */ result = JNI_VERSION_1_4; bail: return result; }2.4 数据类型转换
java的数据类型与native语言的数据类型是有一个转换规则的。
在JNI中,提供了以下各种数据类型,可以分为原生类型和引用类型: 对于原生类型有:jchar, jbyte, jshort, jint, jlong, jfloat, jdouble, jboolean,其与java端的数据类型对应如下表:
java | jni |
char | jchar |
byte | jbyte |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
boolean | jboolean |
2.5 介绍上面中出现过的JNIEnv结构体
从调用JNI_OnLoad函数开始注册起,在函数的不断调用过程中一直伴随着这个结构体类型的变量。所以下面开始介绍它:
这个一个与线程相关的代表JNI环境的结构体。首先看JNIEnv的体系结构:
JNIEnv首先指向一个线程相关的结构,该结构又指向一个指针数组,在这个指针数组中的每个元素最终指向一个JNI函数。所以可以通过JNIEnv去调用JNI函数。
打开jni.h ,libnativehelper\include\nativehelper
struct _JNIEnv; struct _JavaVM; typedef const struct JNINativeInterface* C_JNIEnv; #if defined(__cplusplus) typedef _JNIEnv JNIEnv; typedef _JavaVM JavaVM; #else typedef const struct JNINativeInterface* JNIEnv; typedef const struct JNIInvokeInterface* JavaVM; #endif在jni.h中,为了兼容C和C++两种代码,使用宏__cplusplus加以区分。关于_JNIEvn的定义可以继续看代码,最终的实现时基于虚拟机的。
JNIEnv只在当前线程中有效。本地方法不能将JNIEnv从一个线程传递到另一个线程中。相同的 Java 线程中对本地方法多次调用时,传递给该本地方法的JNIEn是相同的。但是,一个本地方法可被不同的 Java 线程所调用,因此可以接受不同的 JNIEnv。http://book.51cto.com/art/201305/395882.htm
JNIEvn的作用:调用Java的函数;操作jobject对象……(怎么实现这些功能的呢,主要是通过函数获取jobject的成员变量和成员函数,再调用不同函数控制。PS:个人觉得有点象java中通过反射控制对象。)
2.6 JNI中的垃圾回收
JNI技术提供了三种类型引用:
Local Reference
Global Reference
Weak Global Reference
2.7 JNI异常
如果调用JNIEvn的某些函数出错了,会产生异常,但是不会中断本地函数的执行,直到从JNI返回到JAVA层才会抛出这个异常。可能会导致程序死掉。
相关函数:
ExceptionOccured
ExceptionClear
ThrowNew