热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android源码深入理解JNI技术的概述和应用

本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是JavaNativeInterface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

9/5/2016 2:10:30 PM

android源码-深入理解JNI技术

本章涉及的源代码文件名及位置

AndroidRunTime.cpp (framework/base/core/jni/AndroidRunTime.cpp)
JNIHelp.c (libnativehelper/JNIHelp.c)

源码的下载可参考上一篇博客:android源码-下载与管理

1.1 概述

JNI,是Java Native Interface的缩写,中文为Java本地调用。通俗地说,JNI是一种技术,通过这种技术可以做到以下两点:

  • Java程序中的函数可以调用Native语言写的函数,Native一般指的是C/C++编写的函数。
  • Native程序中的函数可以调用Java层的函数,也就是在C/C++程序中可以调用Java的函数。

在Android平台上,JNI就是一座将Native世界和Java世界间的天堑变成通途的桥,它展示了Android平台上JNI所处的位置:

Android平台中JNI示意图

由上图可知,JNI将Java世界和Native世界紧密地联系在一起了。在Android平台上尽情使用Java开发的程序员们不要忘了,如果没有JNI的支持,我们将寸步难行!

1.2 Java世界-互通JNI

Java世界与Native世界通过JNI互通,Java世界与JNI之间建立直接互通通道,主要需要完成了两个比较重要的要点

(1) 加载JNI库

Java要调用Native函数,就必须通过一个位于JNI层的动态库才能做到。顾名思义,动态库就是运行时加载的库,那么是什么时候,在什么地方加载这个库呢?

这个问题没有标准答案,原则上是在调用native函数前,任何时候、任何地方加载都可以。通行的做法是,在类的static语句中加载,通过调用System.loadLibrary方法就可以了。System.loadLibrary函数的参数是动态库的名字,即xxx_jni。系统会自动根据不同的平台拓展成真实的动态拓展名,例如在Linux系统上会拓展成xxx_jni.so,而在Windows平台上则会拓展成xxx_jni.dll

(2) Java的native函数调用

Java函数前都有Java的关键字native,它表示这两个函数将由JNI层来实现。 所以对于Java程序员来说,使用JNI技术真的是太容易了。不过JNI层可没这么轻松,下面来看MS的JNI层分析。

1.3 Native世界-互通JNI

对于JNI桥接来讲,最大的疑惑可能是,怎么会知道Java层的native_xx函数对应的是JNI层的什么函数呢,对应关系是什么?

1.2.1 注册JNI函数

上面的问题其实讨论的是JNI函数的注册问题,“注册”之意就是将Java层的native函数和JNI层对应的实现函数关联起来,有了这种关联,调用Java层的native函数时,就能顺利转到JNI层对应的函数执行了。而JNI函数的注册实际上有两种方法,下面分别做介绍。

(1) 静态方法调用

我们从网上找到的与JNI有的关资料,一般都会介绍如何使用这种方法完成JNI函数的注册,这种方法就是根据函数名来找对应的JNI函数。这种方法需要Java的工具程序javah参与,整体流程如下:

  • 先编写Java代码,然后编译生成.class文件。
  • 使用Java的工具程序javah,如javah–o output packagename.classname ,这样它会生成一个叫output.h的JNI层头文件。其中packagename.classname是Java代码编译后的class文件,而在生成的output.h文件里,声明了对应的JNI层函数,只要实现里面的函数即可。

这个头文件的名字一般都会使用packagename_class.h的样式。

javah com.xio.HelloWorld

得到如下声明:

#include #ifndef _Included_com_xio_HelloWorld
#define _Included_com_xio_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif/*
* Class: com_xio_HelloWorld
* Method: printJNI
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_xio_HelloWorld_print_ljni
(JNIEnv *, jobject, jstring);#ifdef __cplusplus
}
#endif
#endif

从上面代码中可以发现,printJNI的JNI层函数被声明成:

// Java层函数名中如果有一个”_”的话,转换成JNI后就变成了”_l”。
JNIEXPORT jstring JNICALL Java_com_xio_HelloWorld_print_ljni

需解释一下,静态方法中native函数是如何找到对应的JNI函数的。其实,过程非常简单:

  • 当Java层调用print_jni函数时,它会从对应的JNI库Java_com_xio_HelloWorld_print_ljni,如果没有,就会报错。如果找到,则会为这个print_jniJava_com_xio_HelloWorld_print_ljni建立一个关联关系,其实就是保存JNI层函数的函数指针。以后再调用print_jni函数时,直接使用这个函数指针就可以了,当然这项工作是由虚拟机完成的。

从这里可以看出,静态方法就是根据函数名来建立Java函数和JNI函数之间的关联关系的,它要求JNI层函数的名字必须遵循特定的格式。这种方法也有几个弊端,它们是:

  • 需要编译所有声明了native函数的Java类,每个生成的class文件都得用javah生成一个头文件。
  • javah生成的JNI层函数名特别长,书写起来很不方便。
  • 初次调用native函数时要根据函数名字搜索对应的JNI层函数来建立关联关系,这样会影响运行效率。

有什么办法可以克服上面三种弊端吗?根据上面的介绍,Java native函数是通过函数指针来和JNI层函数建立关联关系的。如果直接让native函数知道JNI层对应函数的函数指针,不就万事大吉了吗?这就是下面要介绍的第二种方法:动态注册法。

(2) 动态注册

既然Java native函数数和JNI函数是一一对应的,那么是不是会有一个结构来保存这种关联关系呢?答案是肯定的。在JNI技术中,用来记录这种一一对应关系的,是一个叫JNINativeMethod的结构,其定义如下:

typedef struct {constchar* name; const char* signature;void* fnPtr;
} JNINativeMethod;// 第一个变量name是Java中函数的名字。
// 第二个变量signature,用字符串是描述了Java中函数的参数和返回值
// 第三个变量fnPtr是函数指针,指向native函数。前面都要接 (void *)

应该如何使用这个结构体呢?来看JNI层是如何做的,代码如下所示:

//定义一个JNINativeMethod数组,其成员就是MS中所有native函数的一一对应关系。

static JNINativeMethod gMethods[] = {// Java中native函数的函数名。"print_jni" //print_jni的签名信息"()V", //JNI层对应函数指针。(void*)com_xio_HelloWorld_printJni };int com_xio_printJni(JNIEnv*env) {//调用AndroidRuntime的registerNativeMethods函数 returnAndroidRuntime::registerNativeMethods(env,"com/xio/HelloWorld", gMethods, NELEM(gMethods));}

AndroidRunTime类提供了一个registerNativeMethods函数来完成注册工作,下面看registerNativeMethods的实现,代码如下:

[-->AndroidRunTime.cpp]

int AndroidRuntime::registerNativeMethods(JNIEnv*env, constchar* className, const JNINativeMethod* gMethods, int numMethods) {//调用jniRegisterNativeMethods函数完成注册returnjniRegisterNativeMethods(env, className, gMethods, numMethods);
}

其中jniRegisterNativeMethods是Android平台中,为了方便JNI使用而提供的一个帮助函数,其代码如下所示:

[-->JNIHelp.c]

int jniRegisterNativeMethods(JNIEnv* env, constchar* className, constJNINativeMethod* gMethods, int numMethods) {jclassclazz;clazz&#61; (*env)->FindClass(env, className);......//实际上是调用JNIEnv的RegisterNatives函数完成注册的if((*env)->RegisterNatives(env, clazz, gMethods, numMethods) <0) {return -1;}return0;}

其实动态注册的工作&#xff0c;只用两个函数就能完成。总结如下&#xff1a;

jclass clazz &#61; (*env)->FindClass(env, className);//调用JNIEnv的RegisterNatives函数&#xff0c;注册关联关系。(*env)->RegisterNatives(env, clazz, gMethods,numMethods);

当Java层通过System.loadLibrary加载完JNI动态库后&#xff0c;紧接着会查找该库中一个叫JNI_OnLoad的函数&#xff0c;如果有&#xff0c;就调用它&#xff0c;而动态注册的工作就是在这里完成的。

所以&#xff0c;如果想使用动态注册方法&#xff0c;就必须要实现JNI_OnLoad函数&#xff0c;只有在这个函数中&#xff0c;才有机会完成动态注册的工作。静态注册则没有这个要求&#xff0c;可我建议读者也实现这个JNI_OnLoad函数&#xff0c;因为有一些初始化工作是可以在这里做的。

jint JNI_OnLoad(JavaVM* vm, void* reserved) {//该函数的第一个参数类型为JavaVM,这可是虚拟机在JNI层的代表喔&#xff0c;每个Java进程只有一个JNIEnv* env &#61; NULL;if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) !&#61; JNI_OK) {goto bail;}assert(env !&#61; NULL);if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) <0) {goto bail;}return JNI_VERSION_1_4;//必须返回这个值&#xff0c;否则会报错。
}

JNI函数注册的内容介绍完了。

注意&#xff1a;JNI层代码中一般要包含jni.h这个头文件。Android源码中提供了一个帮助头文件JNIHelp.h&#xff0c;它内部其实就包含了jni.h&#xff0c;所以我们在自己的代码中直接包含这个JNIHelp.h即可。

1.2.2 基本类型的转换

通过前面的分析&#xff0c;解决了JNI函数的注册问题。下面来研究数据类型转换的问题。在Java中调用native函数传递的参数是Java数据类型&#xff0c;那么这些参数类型到了JNI层会变成什么呢&#xff1f;

Java数据类型分为基本数据类型引用数据类型两种&#xff0c;JNI层也是区别对待这二者的。先来看基本数据类型的转换。

基本类型的转换很简单&#xff0c;可用表1表示&#xff1a;

表1 基本数据类型转换关系表

|Java |Native类型 |符号属性 |字长 |--| |boolean |jboolean |无符号 |8位 |byte |jbyte |无符号 |8位 |char |jchar |无符号 |16位 |short |jshort |有符号 |16位 |int |jint |有符号 |32位 |long |jlong |有符号 |64位 |float |jfloat |有符号 |32位 |double |jdouble |有符号 |64位

上面列出了Java基本数据类型和JNI层数据类型对应的转换关系&#xff0c;非常简单。不过&#xff0c;应务必注意&#xff0c;转换成Native类型后对应数据类型的字长&#xff0c;例如jchar在Native语言中是16位&#xff0c;占两个字节&#xff0c;这和普通的char占一个字节的情况完全不一样。

接下来看Java引用数据类型的转换&#xff0c;引用数据类型的转换如表2-2所示&#xff1a;

表2-2 Java引用数据类型转换关系表

|Java引用类型 |Native类型 |Java引用类型 |Native类型 |--| |All objects |jobject |char[] |jcharArray |java.lang.Class实例 |jclass |short[] |jshortArray |java.lang.String实例|jstring |int[] |jintArray |Object[] |jobjectArray |long[] |jlongArray |boolean[] |jbooleanArray |float[] |floatArray |byte[] |jbyteArray |double[] |jdoubleArray |java.lang.Throwable实例|jthrowable

由上表可知&#xff1a;除了Java中基本数据类型的数组、Class、String和Throwable外&#xff0c;其余所有Java对象的数据类型在JNI中都用jobject表示。这一点太让人惊讶了&#xff01;

1.2.3 JNIEnv介绍

JNIEnv是一个和线程相关的&#xff0c;代表JNI环境的结构体&#xff0c;如图展示了JNIEnv的内部结构&#xff1a;

JNIEnv内部结构简图

从上图可知&#xff0c;JNIEnv实际上就是提供了一些JNI系统函数。通过这些函数可以做到&#xff1a;

  • 调用Java的函数。
  • 操作jobject对象等很多事情。

这里&#xff0c;先介绍一个关于JNIEnv的重要知识点。

上面提到说JNIEnv&#xff0c;是一个和线程有关的变量。也就是说&#xff0c;线程A有一个JNIEnv&#xff0c;线程B有一个JNIEnv。由于线程相关&#xff0c;所以不能在线程B中使用线程A的JNIEnv结构体。读者可能会问&#xff0c;JNIEnv不都是native函数转换成JNI层函数后由虚拟机传进来的吗&#xff1f;使用传进来的这个JNIEnv总不会错吧&#xff1f;是的&#xff0c;在这种情况下使用当然不会出错。不过当后台线程收到一个网络消息&#xff0c;而又需要由Native层函数主动回调Java层函数时&#xff0c;JNIEnv是从何而来呢&#xff1f;根据前面的介绍可知&#xff0c;我们不能保存另外一个线程的JNIEnv结构体&#xff0c;然后把它放到后台线程中来用。这该如何是好&#xff1f;

还记得前面介绍的那个JNI_OnLoad函数吗&#xff1f;它的第一个参数是JavaVM&#xff0c;它是虚拟机在JNI层的代表&#xff0c;代码如下所示&#xff1a;

//全进程只有一个JavaVM对象&#xff0c;所以可以保存&#xff0c;任何地方使用都没有问题。
jint JNI_OnLoad(JavaVM* vm, void* reserved)

正如上面代码所说&#xff0c;不论进程中有多少个线程&#xff0c;JavaVM却是独此一份&#xff0c;所以在任何地方都可以使用它。那么&#xff0c;JavaVM和JNIEnv又有什么关系呢&#xff1f;答案如下&#xff1a;

  • 调用JavaVM的AttachCurrentThread函数&#xff0c;就可得到这个线程的JNIEnv结构体。这样就可以在后台线程中回调Java函数了。
  • 另外&#xff0c;后台线程退出前&#xff0c;需要调用JavaVM的DetachCurrentThread函数来释放对应的资源。

再来看JNIEnv的作用。

1.2.4 通过JNIEnv操作jobject

前面提到过一个问题&#xff0c;即Java的引用类型除了少数几个外&#xff0c;最终在JNI层都用jobject来表示对象的数据类型&#xff0c;那么该如何操作这个jobject呢&#xff1f;

从另外一个角度来解释这个问题。一个Java对象是由什么组成的&#xff1f;当然是它的成员变量和成员函数了。那么&#xff0c;操作jobject的本质就应当是操作这些对象的成员变量和成员函数。所以应先来看与成员变量及成员函数有关的内容。

(1) jfieldID 和jmethodID的介绍

我们知道&#xff0c;成员变量和成员函数是由类定义的&#xff0c;它是类的属性&#xff0c;所以在JNI规则中&#xff0c;用jfieldID和jmethodID来表示Java类的成员变量和成员函数&#xff0c;它们通过JNIEnv的下面两个函数可以得到&#xff1a;

jfieldID GetFieldID(jclass clazz,const char*name, const char *sig);jmethodID GetMethodID(jclass clazz, const char*name,const char *sig);

其中&#xff0c;jclass代表Java类&#xff0c;name表示成员函数或成员变量的名字&#xff0c;sig为这个函数和变量的签名信息。如前所示&#xff0c;成员函数和成员变量都是类的信息&#xff0c;这两个函数的第一个参数都是jclass。

在上面代码中&#xff0c;将scanFile和handleStringTag函数的jmethodID保存为MyMediaScannerClient的成员变量。为什么这里要把它们保存起来呢&#xff1f;这个问题涉及一个事关程序运行效率的知识点&#xff1a;

· 如果每次操作jobject前都去查询jmethoID或jfieldID的话将会影响程序运行的效率。所以我们在初始化的时候&#xff0c;就可以取出这些ID并保存起来以供后续使用。

取出jmethodID后&#xff0c;又该怎么用它呢&#xff1f;

(2) 使用jfieldID和jmethodID

通过JNIEnv输出的CallVoidMethod&#xff0c;再把jobject、jMethodID和对应参数传进去&#xff0c;JNI层就能够调用Java对象的函数了&#xff01;

实际上JNIEnv输出了一系列类似CallVoidMethod的函数&#xff0c;形式如下&#xff1a;

NativeType CallMethod(JNIEnv *env,jobject obj,jmethodID methodID, ...)

其中type是对应Java函数的返回值类型&#xff0c;例如CallIntMethod、CallVoidMethod等。

上面是针对非static函数的&#xff0c;如果想调用Java中的static函数&#xff0c;则用JNIEnv输出的CallStaticMethod系列函数。

现在&#xff0c;我们已了解了如何通过JNIEnv操作jobject的成员函数&#xff0c;那么怎么通过jfieldID操作jobject的成员变量呢&#xff1f;这里&#xff0c;直接给出整体解决方案&#xff0c;如下所示&#xff1a;

//获得fieldID后&#xff0c;可调用GetField系列函数获取jobject对应成员变量的值。NativeType GetField(JNIEnv *env,jobject obj,jfieldID fieldID)//或者调用SetField系列函数来设置jobject对应成员变量的值。void SetField(JNIEnv *env,jobject obj,jfieldID fieldID,NativeType value)

下面我们列出一些参加的Get/Set函数

|Get |Set| |--| |GetObjectField() |SetObjectField()| |GetBooleanField() |SetBooleanField()| |GetByteField() |SetByteField()| |GetCharField() |SetCharField()| |GetShortField() |SetShortField()| |GetIntField() |SetIntField()| |GetLongField() |SetLongField()| |GetFloatField() |SetFloatField()| |GetDoubleField() |SetDoubleField()|

通过本节的介绍&#xff0c;相信读者已了解jfieldIDjmethodID的作用&#xff0c;也知道如何通过JNIEnv的函数来操作jobject了。虽然jobject是透明的&#xff0c;但有了JNIEnv的帮助&#xff0c;还是能轻松操作jobject背后的实际对象了。

1.2.5 jstring介绍

Java中的String也是引用类型&#xff0c;不过由于它的使用非常频繁&#xff0c;所以在JNI规范中单独创建了一个jstring类型来表示Java中的String类型。虽然jstring是一种独立的数据类型&#xff0c;但是它并没有提供成员函数供操作。相比而言&#xff0c;C&#43;&#43;中的string类就有自己的成员函数了。那么该怎么操作jstring呢&#xff1f;还是得依靠JNIEnv提供的帮助。这里看几个有关jstring的函数&#xff1a;

  • 调用JNIEnv的NewString(JNIEnv env, const jcharunicodeChars,jsize len)&#xff0c;可以从Native的字符串得到一个jstring对象。其实&#xff0c;可以把一个jstring对象看成是Java中String对象在JNI层的代表&#xff0c;也就是说&#xff0c;jstring就是一个Java String。但由于Java String存储的是Unicode字符串&#xff0c;所以NewString函数的参数也必须是Unicode字符串。

  • 调用JNIEnv的NewStringUTF将根据Native的一个UTF-8字符串得到一个jstring对象。在实际工作中&#xff0c;这个函数用得最多。

  • 上面两个函数将本地字符串转换成了Java的String对象&#xff0c;JNIEnv还提供了GetStringChars和GetStringUTFChars函数&#xff0c;它们可以将Java String对象转换成本地字符串。其中GetStringChars得到一个Unicode字符串&#xff0c;而GetStringUTFChars得到一个UTF-8字符串。

  • 另外&#xff0c;如果在代码中调用了上面几个函数&#xff0c;在做完相关工作后&#xff0c;就都需要调用ReleaseStringChars或ReleaseStringUTFChars函数对应地释放资源&#xff0c;否则会导致JVM内存泄露。这一点和jstring的内部实现有关系&#xff0c;读者写代码时务必注意这个问题。

1.2.6 JNI类型签名的介绍

先来看动态注册中的一段代码&#xff1a;

static JNINativeMethod gMethods[] &#61; {// Java中native函数的函数名。"print_jni" //print_jni的签名信息"()V", //JNI层对应函数指针。(void*)com_xio_HelloWorld_printJni };

上面代码中的JNINativeMethod已经见过了&#xff0c;不过其中那个字符串"()V"&#xff0c;根据前面的介绍可知&#xff0c;它是Java中对应函数的签名信息&#xff0c;由参数类型和返回值类型共同组成。不过为什么需要这个签名信息呢&#xff1f;

  • 这个问题的答案比较简单。因为Java支持函数重载&#xff0c;也就是说&#xff0c;可以定义同名但不同参数的函数。但仅仅根据函数名&#xff0c;是没法找到具体函数的。为了解决这个问题&#xff0c;JNI技术中就使用了参数类型和返回值类型的组合&#xff0c;作为一个函数的签名信息&#xff0c;有了签名信息和函数名&#xff0c;就能很顺利地找到Java中的函数了。

JNI规范定义的函数签名信息看起来很别扭&#xff0c;不过习惯就好了。它的格式是&#xff1a;

(参数1类型标示参数2类型标示...参数n类型标示)返回值类型标示。

当参数的类型是引用类型时&#xff0c;其格式是”L包名;”&#xff0c;其中包名中的”.”换成”/”。上面例子中的

Ljava/lang/String;表示是一个Java String类型。

函数签名不仅看起来麻烦&#xff0c;写起来更麻烦&#xff0c;稍微写错一个标点就会导致注册失败。所以&#xff0c;在具体编码时&#xff0c;读者可以定义字符串宏&#xff0c;这样改起来也方便。

表1 类型标示示意表

|类型标示 |Java类型 |类型标示 |Java类型| |--| |Z |boolean |F |float| |B |byte |D |double| |C |char |L/java/langaugeString; |String| |S |short |[I |int[]| |I |int |[L/java/lang/object; |Object[]| |J |long

上面列出了一些常用的类型标示。请读者注意&#xff0c;如果Java类型是数组&#xff0c;则标示中会有一个“[”&#xff0c;另外&#xff0c;引用类型&#xff08;除基本类型的数组外&#xff09;的标示最后都有一个“;”。

再来看一些小例子&#xff0c;如表2所示&#xff1a;

表2 函数签名小例子

|函数签名 |Java函数 | |--| |“()Ljava/lang/String;” |String f() | |“(ILjava/lang/Class;)J”|long f(int i, Class c)| |“([B)V” |void f(byte[] bytes)|

虽然函数签名信息很容易写错&#xff0c;但Java提供一个叫javap的工具能帮助生成函数或变量的签名信息&#xff0c;它的用法如下&#xff1a;

javap –s -p xxx。其中xxx为编译后的class文件&#xff0c;s表示输出内部数据类型的签名信息&#xff0c;p表示打印所有函数和成员的签名信息&#xff0c;而默认只会打印public成员和函数的签名信息。

有了javap&#xff0c;就不用死记硬背上面的类型标示了。

1.2.7 垃圾回收

我们知道&#xff0c;Java中创建的对象最后是由垃圾回收器来回收和释放内存的&#xff0c;可它对JNI有什么影响呢&#xff1f;下面看一个例子&#xff1a;

[-->垃圾回收例子]

static jobject save_thiz &#61; NULL; //定义一个全局的jobjectstatic void com_xio_HelloWorld_print_jni(JNIEnv*env, jobject thiz, jstring path,jstringmimeType, jobject client) {//保存Java层传入的jobject对象&#xff0c;代表MediaScanner对象save_thiz &#61; thiz;return;}//假设在某个时间&#xff0c;有地方调用callMediaScanner函数void function1() {//在这个函数中操作save_thiz&#xff0c;会有问题吗&#xff1f;}

上面的做法肯定会有问题&#xff0c;因为和save_thiz对应的Java层中的对象很有可能已经被垃圾回收了&#xff0c;也就是说&#xff0c;save_thiz保存的这个jobject可能是一个野指针&#xff0c;如使用它&#xff0c;后果会很严重。

可能有人要问&#xff0c;将一个引用类型进行赋值操作&#xff0c;它的引用计数不会增加吗&#xff1f;而垃圾回收机制只会保证那些没有被引用的对象才会被清理。问得对&#xff0c;但如果在JNI层使用下面这样的语句&#xff0c;是不会增加引用计数的。

save_thiz &#61; thiz; //这种赋值不会增加jobject的引用计数。

那该怎么办&#xff1f;不必担心&#xff0c;JNI规范已很好地解决了这一问题&#xff0c;JNI技术一共提供了三种类型的引用&#xff0c;它们分别是&#xff1a;

  • Local Reference&#xff1a;本地引用。在JNI层函数中使用的非全局引用对象都是Local Reference。它包括函数调用时传入的jobject、在JNI层函数中创建的jobject。LocalReference最大的特点就是&#xff0c;一旦JNI层函数返回&#xff0c;这些jobject就可能被垃圾回收。

  • Global Reference&#xff1a;全局引用&#xff0c;这种对象如不主动释放&#xff0c;就永远不会被垃圾回收。

  • Weak Global Reference&#xff1a;弱全局引用&#xff0c;一种特殊的GlobalReference&#xff0c;在运行过程中可能会被垃圾回收。所以在程序中使用它之前&#xff0c;需要调用JNIEnv的IsSameObject判断它是不是被回收了。

平时用得最多的是Local Reference和Global Reference&#xff0c;下面看一个实例&#xff0c;代码如下所示&#xff1a;

xxClient(JNIEnv *env, jobjectclient):mEnv(env),//调用NewGlobalRef创建一个GlobalReference,这样mClient就不用担心被回收了。mClient(env->NewGlobalRef(client)) {}//析构函数virtual ~MyMediaScannerClient() {mEnv->DeleteGlobalRef(mClient);//调用DeleteGlobalRef释放这个全局引用。}

每当JNI层想要保存Java层中的某个对象时&#xff0c;就可以使用Global Reference&#xff0c;使用完后记住释放它就可以了。

因为Local Reference得回收并非是及时的&#xff0c;如果没有及时回收的Local Reference或许是进程占用过多的一个原因&#xff0c;请务必注意这一点。

1.2.8 JNI中的异常处理

JNI中也有异常&#xff0c;不过它和C&#43;&#43;、Java的异常不太一样。当调用JNIEnv的某些函数出错后&#xff0c;会产生一个异常&#xff0c;但这个异常不会中断本地函数的执行&#xff0c;直到从JNI层返回到Java层后&#xff0c;虚拟机才会抛出这个异常。虽然在JNI层中产生的异常不会中断本地函数的运行&#xff0c;但一旦产生异常后&#xff0c;就只能做一些资源清理工作了&#xff08;例如释放全局引用&#xff0c;或者ReleaseStringChars&#xff09;。如果这时调用除上面所说函数之外的其他JNIEnv函数&#xff0c;则会导致程序死掉。

JNI层函数可以在代码中截获和修改这些异常&#xff0c;JNIEnv提供了三个函数进行帮助&#xff1a;

  • ExceptionOccured函数&#xff0c;用来判断是否发生异常。

  • ExceptionClear函数&#xff0c;用来清理当前JNI层中发生的异常。

  • ThrowNew函数&#xff0c;用来向Java层抛出异常。

异常处理是JNI层代码必须关注的事情&#xff0c;读者在编写代码时务小心对待。

1.4 本章小结

本章通过一个实例介绍了JNI技术中的几个重要方面&#xff0c;包括&#xff1a;

  • java层桥接JNI函数注册的方法。

  • Native如何与JNI建立联系

  • Java和JNI层数据类型的转换。

  • JNIEnv和jstring的使用方法&#xff0c;以及JNI中的类型签名。

  • 最后介绍了垃圾回收在JNI层中的使用&#xff0c;以及异常处理方面的知识。

##参考文献

《深入理解Android卷I》


转:https://my.oschina.net/feiyangxiaomi/blog/743477



推荐阅读
  • 二维码的实现与应用
    本文介绍了二维码的基本概念、分类及其优缺点,并详细描述了如何使用Java编程语言结合第三方库(如ZXing和qrcode.jar)来实现二维码的生成与解析。 ... [详细]
  • JUnit下的测试和suite
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 如何在PHP中安装Xdebug扩展
    本文介绍了如何从PECL下载并编译安装Xdebug扩展,以及如何配置PHP和PHPStorm以启用调试功能。 ... [详细]
  • 在将 Android Studio 从 3.0 升级到 3.1 版本后,遇到项目无法正常编译的问题,具体错误信息为:org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:processDemoProductDebugResources'。 ... [详细]
  • 为何Compose与Swarm之后仍有Kubernetes的诞生?
    探讨在已有Compose和Swarm的情况下,Kubernetes是如何以其独特的设计理念和技术优势脱颖而出,成为容器编排领域的领航者。 ... [详细]
  • 函子(Functor)是函数式编程中的一个重要概念,它不仅是一个特殊的容器,还提供了一种优雅的方式来处理值和函数。本文将详细介绍函子的基本概念及其在函数式编程中的应用,包括如何通过函子控制副作用、处理异常以及进行异步操作。 ... [详细]
  • Maven + Spring + MyBatis + MySQL 环境搭建与实例解析
    本文详细介绍如何使用MySQL数据库进行环境搭建,包括创建数据库表并插入示例数据。随后,逐步指导如何配置Maven项目,整合Spring框架与MyBatis,实现高效的数据访问。 ... [详细]
  • publicclassBindActionextendsActionSupport{privateStringproString;privateStringcitString; ... [详细]
  • spring boot使用jetty无法启动 ... [详细]
  • 从理想主义者的内心深处萌发的技术信仰,推动了云原生技术在全球范围内的快速发展。本文将带你深入了解阿里巴巴在开源领域的贡献与成就。 ... [详细]
  • Windows操作系统提供了Encrypting File System (EFS)作为内置的数据加密工具,特别适用于对NTFS分区上的文件和文件夹进行加密处理。本文将详细介绍如何使用EFS加密文件夹,以及加密过程中的注意事项。 ... [详细]
  • 回顾两年前春节期间的一个个人项目,该项目原本计划参加竞赛,但最终作为练习项目完成。独自完成了从编码到UI设计的全部工作,尽管代码量不大,但仍有一定的参考价值。本文将详细介绍该项目的背景、功能及技术实现。 ... [详细]
  • importjava.io.*;importjava.util.*;publicclass五子棋游戏{staticintm1;staticintn1;staticfinalintS ... [详细]
  • 深入解析WebP图片格式及其应用
    随着互联网技术的发展,无论是PC端还是移动端,图片数据流量占据了很大比重。尤其在高分辨率屏幕普及的背景下,如何在保证图片质量的同时减少文件大小,成为了亟待解决的问题。本文将详细介绍Google推出的WebP图片格式,探讨其在实际项目中的应用及优化策略。 ... [详细]
  • Flutter 核心技术与混合开发模式深入解析
    本文深入探讨了 Flutter 的核心技术,特别是其混合开发模式,包括统一管理模式和三端分离模式,以及混合栈原理。通过对比不同模式的优缺点,帮助开发者选择最适合项目的混合开发策略。 ... [详细]
author-avatar
dmcm0009
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有