JNI(深入理解Android卷I)的读书笔记

时间:2022-04-19 00:58:36

一:概述

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

对于引用类型则有:jobject, jstring, jthrowable, jclass, jarray, 以及继承于jarray,对应于其原生类型的8种jarray和jobjectarray。

JNI(深入理解Android卷I)的读书笔记

2.5 介绍上面中出现过的JNIEnv结构体
从调用JNI_OnLoad函数开始注册起,在函数的不断调用过程中一直伴随着这个结构体类型的变量。所以下面开始介绍它:

这个一个与线程相关的代表JNI环境的结构体。首先看JNIEnv的体系结构:

JNI(深入理解Android卷I)的读书笔记

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