一:概述
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需要注册JNI函数,在java层调用native函数时,才能顺利转到JNI层对应函数执行。
android_media_MediaScanner_processDirectory(
JNIEnv* env, jobject thiz, jstring path, jobject client){……}
注册有两种方法:
1:静态注册:根据文件名确立Java函数和JNI函数的关联关系。再通过函数指针操作。有弊端:效率低,JNI函数名长……
2:动态注册:利用结构体JNINativeMethod保存某个Java native函数和对应的JNI 函数的关联关系。
多对关联关系通过JNINativeMethod类型数组来保存。
下面详细介绍动态注册:
注册过程的实现:
1:JNINativeMethod结构体
打开jni.h ,libnativehelper\include\nativehelper
typedef struct {2:构建JNINativeMethod类型数组来表示多对Java native函数和对应的JNI 函数的关联关系。
const char* name; //Java中函数的名字
const char* signature; //签名信息即用字符串描述的函数的参数和返回值
void* fnPtr; //指向C函数的函数指针
} JNINativeMethod;
打开源码android_media_MediaScanner.cpp,frameworks\base\media\jni
static JNINativeMethod gMethods[] = {上述数组成员与MediaScanner.java中声明的native方法以及android_media_MediaScanner.cpp中的方法是一一对应的。
{
"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
},
};
观察数组成员中的签名信息,如
"(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通过调用registerNativerMethods函数完成注册。registerNativerMethods函数是在AndroidRunTime.cpp中定义的,AndroidRunTime.cpp也是位于JNI层的。
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaScanner(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
kClassMediaScanner, gMethods, NELEM(gMethods));//传入被注册数组
}
打开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,再通过JNIEvn的RegisterNatives函数完成注册。JNIEvn是一个重要的结构体。
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;
}
总结:
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层的代表2.4 数据类型转换
{
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;
}
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 |
对于引用类型则有:jobject, jstring, jthrowable, jclass, jarray, 以及继承于jarray,对应于其原生类型的8种jarray和jobjectarray。
2.5 介绍上面中出现过的JNIEnv结构体
从调用JNI_OnLoad函数开始注册起,在函数的不断调用过程中一直伴随着这个结构体类型的变量。所以下面开始介绍它:
这个一个与线程相关的代表JNI环境的结构体。首先看JNIEnv的体系结构:
JNIEnv首先指向一个线程相关的结构,该结构又指向一个指针数组,在这个指针数组中的每个元素最终指向一个JNI函数。所以可以通过JNIEnv去调用JNI函数。
打开jni.h ,libnativehelper\include\nativehelper
struct _JNIEnv;在jni.h中,为了兼容C和C++两种代码,使用宏__cplusplus加以区分。关于_JNIEvn的定义可以继续看代码,最终的实现时基于虚拟机的。
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
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