到目前为止,我已将JNI环境和作业对象保存在本地。我发现,为了我的JNI运行ICS和up设备,我需要修复我的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;
答案 0 :(得分:8)
首先:myEnv = (*env)->NewGlobalRef(env,obj);
是错的。 You mustn't cache this value
您被允许的是缓存方法ID,字段ID,类引用,...(但请确保您之后清理这些内容)。但缓存这些值需要采取特殊措施。
为什么?问题是允许JVM根据程序的需要加载和卸载类。因此,一旦类的最后一个实例被垃圾收集器销毁,就会发生类的卸载。一旦发生这种情况,您的缓存ID就不再有效。在JVM再次加载类之后,ID可能会相同,但这不能保证。
解决方案:如果要缓存这些ID,则必须告诉JVM不允许卸载类。这正是NewGlobalRef
的作用。您只需递增传递给NewGlobalRef
的引用的引用,以便引用计数永远不会降为零,并且不允许使用grabage collection来清理引用的元素。
注意:创建NewGlobalRef
有一个严重的缺点:除了Java之外,如果您不再需要此引用,则必须确保调用DeleteGlobalRef
为了重新启用引用的垃圾收集。 (因为垃圾收集者不知道你是否还需要这个参考)或换句话说:你必须确保自己清理垃圾,否则会留下内存泄漏。
我还说为对象创建一个全局引用并不是一个好主意(除非你真的想保持对象存活),因为这意味着对象不会进入垃圾,因此永远不会被释放
更好的变体:如果要缓存这些ID以加快对某个对象的访问,请保留该类的全局引用(使用FindClass
)并从中获取ID类对象FindClass
返回。
这是我的意思的一个(不完整的)例子。我通常创建一个结构,其中包含访问类所需的所有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通话的文档