到目前为止,我已经将 JNI 环境和 jobject 对象保存在本地。我发现为了让我的 JNI 运行 ICS 和更高版本的设备,我需要修复我的 JNI 代码。这是我得到的错误:
02-20 10:20:59.523: E/dalvikvm(21629): JNI ERROR (app bug): attempt to use stale local reference 0x38100019
02-20 10:20:59.523: E/dalvikvm(21629): VM aborting
02-20 10:20:59.523: A/libc(21629): Fatal signal 11 (SIGSEGV) at 0xdeadd00d (code=1), thread 21629
我对如何创建/销毁这些全局变量感到困惑,甚至不知道我是否做对了。
我的应用程序目前在所有使用此代码的 ICS 之前的设备上运行良好:
BYTE Java_my_eti_commander_RelayAPIModel_00024NativeCalls_InitRelayJava( JNIEnv *env, jobject obj ) {
myEnv = (env);
myObject = obj;
changeID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "changeItJavaWrapper", "(S)V" );
getID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "getItJavaWrapper" , "(S)S" );
putID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "putItJavaWrapper" , "(B)V" );
flushID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "flushItJavaWrapper" , "()V" );
delayID = (*myEnv)->GetStaticMethodID( myEnv, myObject, "delayItJavaWrapper" , "(S)V" );
RelayAPI_SetBaud= WrapSetBaud;
RelayAPI_get = WrapGetIt;
RelayAPI_put = WrapPutIt;
RelayAPI_flush = WrapFlushIt;
RelayAPI_delay = WrapDelayIt;
...
}
在 GetStaticMethodID
调用下,RelayAPI_ 变量都是指向此处的函数指针:
void WrapSetBaud( WORD w ) {
return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, changeID, w );
}
short WrapGetIt( WORD time ) {
return (*myEnv)->CallStaticShortMethod( myEnv, myObject, getID, time );
}
void WrapPutIt( BYTE buff ) {
return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, putID, buff );
}
void WrapFlushIt( void ) {
return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, flushID );
}
void WrapDelayIt( WORD wait ) {
return (*myEnv)->CallStaticVoidMethod( myEnv, myObject, delayID, wait );
}
最后,它返回到我这里的 Java 代码:
public static void changeItJavaWrapper( short l ) throws IOException {
mModelService.changeitJava( l );
}
public static void flushItJavaWrapper() {
mModelService.flushitJava();
}
public static void putItJavaWrapper( byte p ) {
mModelService.putitJava( p );
}
public static void delayItJavaWrapper( short wait ) {
mModelService.delayitJava( wait );
}
public static short getItJavaWrapper( short s ) throws IOException {
return mModelService.getitJava( s );
}
我已将初始化更改为:
myEnv = (*env)->NewGlobalRef(env,obj);
myObject = (*env)->NewGlobalRef(env,obj);
但我对此感到非常困惑,因为它们具有相同的参数,而且它只是没有意义。我在任何地方都找不到这种方法的文档,听起来很愚蠢,this tutorial
, this page
, 和 the oracle docs
没有关于 NewGlobalRef
方法本身的任何信息。
编辑
jmethodID changeID;
jmethodID getID;
jmethodID putID;
jmethodID flushID;
jmethodID delayID;
jobject myObject;
jclass bluetoothClass;
JNIEnv *myEnv;
最佳答案
首先:myEnv = (*env)->NewGlobalRef(env,obj);
是错误的。 You mustn't cache this value .
您可以缓存方法 ID、字段 ID、类引用……(但请确保事后清理这些内容)。但是缓存这些值需要特殊的措施。
为什么?问题是允许JVM根据程序的需要加载和卸载类。因此,一旦类的最后一个实例被垃圾收集器销毁,就可能会卸载该类。一旦发生这种情况,您缓存的 ID 将不再有效。在 JVM 再次加载该类后,ID 可能会相同,但这并不能保证。
解决方案:如果您想缓存这些 ID,您必须告诉 JVM 不允许卸载某个类。这正是 NewGlobalRef
所做的。您只需增加传递给 NewGlobalRef
的引用的引用,这样引用计数就不会降为零,并且不允许抓取收集来清理引用的元素。
注意: 创建 NewGlobalRef
有一个严重的缺点:除了在 Java 中,如果您不这样做,您必须确保调用 DeleteGlobalRef
不再需要此引用以重新启用引用的垃圾回收。 (因为垃圾收集器不知道你是否仍然需要这个引用)或者换句话说:你必须确保自己清理垃圾,否则你会留下内存泄漏。
我还要说,为对象创建全局引用不是一个好主意(除非你真的想让对象保持 Activity 状态),因为这意味着对象永远不会进入垃圾,因此永远不会被释放.
更好的变体:如果您想缓存这些 ID 以加快对某个对象的访问,请保留该类的全局引用(使用 FindClass
)并抓取FindClass
返回的类对象的 ID。
这是我的意思的一个(不完整的)例子。我通常创建一个结构来保存我访问类所需的所有 ID,只是为了保持我的命名空间干净。你可以这样想象:
/*! \brief Holds cached field IDs for MyClass.java */
typedef struct MyClass {
int loaded; /*!< Nonzero if the information are valid */
jclass clazz; /*!< Holds a global ref for the class */
jfieldID aField; /*!< Holds the field ID of aField */
}tMyClass;
static tMyClass me = { 0 };
最简单的方法是为您的对象提供一个“连接”函数,该函数对上面定义的结构进行初始化。
/*! \brief This function fetches field IDs for a specific class in order to have
faster access elsewhere in the code
\param env a valid JNI environment
\return
- 0 if OK
- <0 if an error occured */
int MyClass_connect(JNIEnv *env)
{
jobject theClass = env->FindClass("MyClass");
if (theClass == NULL) goto no_class;
me.clazz = (jclass) env->NewGlobalRef(theClass); // make it global to avoid class unloading and therefore
// invalidating the references obtained.
if (me.clazz == NULL) goto no_memory;
me.aField = env->GetFieldID(me.clazz, "aField", "I") ;
if (me.aField == NULL) goto no_aField;
me.loaded = 1;
return 0;
no_aField:
env->DeleteGlobalRef(me.clazz);
no_memory:
no_class:
return -1;
}
成功调用 MyClass_connect
后,您可以使用 me.aField
缩短代码以访问代码中的字段。当然,你必须提供一个断开函数,当不再需要 MyClass 时调用它:
void MyClass_disconnect(JNIEnv *env)
{
if (me.loaded == 0) return;
env->DeleteGlobalRef(me.clazz);
memset(me, 0, sizeof(tMyClass));
}
抱歉,这篇文章有点冗长,但我希望这有助于解决您的困惑,并让您对 JNI 的内部工作原理有一些了解,并了解如何有效地处理这个问题。
编辑:您可以在 oracle's website 上找到关于 JNI 调用的文档。
关于android - JNIEnv 全局引用与 C 中的 jobject 有何不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15003952/