本文共 4149 字,大约阅读时间需要 13 分钟。
2.5 JNI操作Java对象
JNI提供了Java和C/C++方法互操作的机制,上节只介绍了如何在Java中调用JNI实现方法,那JNI又是如何操作Java层呢?
JNI方法接受的第二个参数是Java对象:jobject,可以在JNI中操作这个jobject进而操作Java对象提供的变量和方法。2.5.1 访问Java对象
要操作jobject,就是要访问这个对象并操作它的变量和方法。JNI提供的类和对象操作函数有很多,常用的有两个:FindClass和GetObjectClass,在C和C++中分别有不同的函数原型。
C++中的函数原型如下:
jclass FindClass(const char name);//查找类信息
jclass GetObjectClass(jobject obj);//返回对象的类C中的函数原型如下: jclass (FindClass)(JNIEnv, const char);jclass (GetObjectClass)(JNIEnv, jobject);我们可以看看Log系统是怎么操作Java对象的。打开android_util_Log.cpp,定位到register_android_util_Log函数:int register_android_util_Log(JNIEnv env){ jclass clazz = env->FindClass("android/util/Log");……}通过给FindClass传入要查找类的全限定类名(以“/”分隔路径)即可,之后方法返回一个jclass的对象,这样就可以操作这个类的方法和变量了。2.5.2 操作成员变量(域)和方法
上节通过JNI提供的类操作函数得到了类的引用,通过这个引用便可以操作这个类上提供的方法和变量。JNI 用名字和类型签名来识别方法和域(变量)。
注意 Java中习惯将变量称为成员变量,而不是域。这里为了兼容JNI命名规则和Java习惯,将域和变量等价。
从名字和类型签名来操作对象上的域和方法可分为两步。还是以Log系统为例。打开android_util_Log.cpp,找到register_android_util_Log方法,代码如下:
int register_android_util_Log(JNIEnv env){jclass clazz = env->FindClass("android/util/Log");levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));……
}
首先,通过FindClass方法找到android/util/Log的类信息clazz;然后,以clazz为参数调用GetStaticFieldID(clazz, "DEBUG", "I"),其中DEBUG是要访问的Java域的名字,I是该Java域的类型签名,即整型。GetStaticFieldID的函数原型如下:
jfieldID GetStaticFieldID(jclass clazz, const char name, const char sig)
该函数返回了一个jfieldID,代表Java成员变量。最后将该jfieldID传给GetStaticIntField方法,得到Java层的成员变量DEBUG的值,即3。下面是Log.java的源码:
public final class Log { ……public static final int DEBUG = 3;……}JNI调用Java层的方法与此类似,流程是:
FindClass->GetMethodID返回(jmethodID)->CallMethod这里仅提供函数列表,不再详细解释。表2-4中列出了JNI提供的操作域和方法的函数。
2.5.3 全局引用、弱全局引用和局部引用
Java对象的生命周期由虚拟机管理,虚拟机内部维护一个对象的引用计数,如果一个对象的引用计数为0,这个对象将被垃圾回收器回收并释放内存。这里就有一个问题,如果Java对象中使用了Native方法,那会对对象的生命周期产生什么影响呢?
回答这个问题前,先看Log系统的例子。代码如下:
//static jobject clazz_ref1 = NULL; 方法1加入的code,见下文对方法1的解释
static jboolean android_util_Log_isLoggable(JNIEnv env, jobject clazz,jstring tag, jint level)
{
……//clazz_ref1 = clazz; 方法1加入的code//static jobject clazz_ref2 = NULL;方法2加入的code//clazz_ref2 = clazz; 方法2加入的codeif ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) { ……//异常处理代码} else { result = isLoggable(chars, level);}……
}
这部分代码中,并没有操作传进来的jobject对象,在这里对其进行修改,加入自己的代码保存传进来的jobject对象。要达到保存jobject对象的目的,C/C++程序员有两种方法:
方法1 在方法外加入全局变量,并在方法内赋值。方法2 在方法内加入静态变量,并赋值。
这两种方法能达到我们的目的吗?
很不幸,答案是不能,而且后果很严重。
因为这样做,虚拟机无法跟踪该对象的引用计数,相当于没有增加引用计数。如果jobject已经被虚拟机回收,clazz_ref1和clazz_ref2将引用一个野指针,C/C++程序员应该知道野指针的问题有多严重。
那既然传统的方法无法保存对象,我们又该怎么做呢?
既然赋值操作无法通知虚拟机增加对象的引用计数,那是不是应该想到JNIEnv能替我们做些什么?因为到目前为止,我们能操作的只有这个接口。
幸运的是,JNIEnv已经为我们提供了解决方案:局部引用、全局引用和弱全局引用。
先来看JNI规范中是怎么定义这三种引用的。1.局部引用
可以增加引用计数,作用范围为本线程,生命周期为一次Native调用。局部引用包括多数JNI函数创建的引用,Native方法返回值和参数。局部引用只在创建它的Native方法的线程中有效,并且只在Native方法的一次调用中有效,在该方法返回后,被虚拟机回收(不同于C中的局部变量,返回后会立即回收)。
2.全局引用
可以增加引用计数。作用范围为多线程,多个Native方法,生命周期到显式释放。全局引用通过JNI函数NewGlobalRef创建,并通过DeleteGlobalRef释放。如果程序员不显式释放,将永远不会被垃圾回收。
3.弱全局引用
不能增加引用计数。作用范围为多线程,多个Native方法,生命周期到显式释放。不过其对应的Java对象生命周期依然取决于虚拟机,意思是即便弱全局引用没有被释放,其引用的Java对象可能已经被释放。弱全局引用通过JNI函数NewWeakGlobalRef创建,并通过DeleteWeakGlobalRef释放。弱全局引用的优点是:既可以保存对象,又不会阻止该对象被回收。
注意 使用弱全局引用的时候,一定要注意:它所指向的对象可能已经被回收了。JNI 提供了IsSameObject函数用来判断弱引用对应的对象是否已经被回收,方法是用弱全局引用和NULL进行比较,如果返回JNI_TRUE,则说明弱全局引用指向的对象已经被回收。
IsSameObject的方法声明如下。
在C中: jboolean (IsSameObject)(JNIEnv,jobject,jobject);在C++中:jboolean IsSameObject(jobject ref1, jobject ref2);假设有一个弱引用weak_gref,可以按照如下方法使用:if(env->IsSameObject(weak_gref,NULL) == JNI_TRUE){ //do something with weak_gref}既然已经知道了JNI中如何保存对象,我们继续修改代码,引入全局引用达到保存对象的目的。修改如下:static jobject g_clazz_ref = NULL;static jboolean android_util_Log_isLoggable(JNIEnv* env,jobject clazz, jstring tag, jint level)
{
……g_clazz_ref = env->NewGlobalRef(clazz);if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) { ……} else { result = isLoggable(chars, level);}
……
}//一定要记住,在不使用该类的时候显式删除env->DeleteGlobalRef(g_clazz_ref);
Android中对局部引用和全局引用的使用都有一定限制。如果引用超过一定数量,或者使用不当,非常容易引起内存不足和内存泄露问题。
对于全局引用,默认不能超过2000个,否则会出现内存不足的警告。如果在Dalvik的启动参数dalvik.vm.checkjni中设置打开checkjni的选项,Dalvik将监控全局引用的数量,如果超过2000, 在logcat中会看到“GREF overflow”,提示内存不足。GREF便是全局引用的缩写。
转载地址:http://mfbll.baihongyu.com/