1

到目前为止,我已经在本地保存了我的 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

我对如何创建/销毁这些全局变量以及我是否做得对感到困惑。

我的应用程序目前在所有使用此代码的 pre-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;
4

1 回答 1

8

首先:myEnv = (*env)->NewGlobalRef(env,obj);是错误的。你不能缓存这个值

您可以缓存方法 ID、字段 ID、类引用……(但请确保之后清理这些内容)。但是缓存这些值需要特殊措施。

为什么?问题是JVM被允许根据程序的需要加载和卸载类。因此,一旦类的最后一个实例被垃圾收集器销毁,就可能会卸载一个类。一旦发生这种情况,您的缓存 ID 将不再有效。在 JVM 再次加载类后,ID 可能会相同,但这不能保证。

解决方案:如果您想缓存这些 ID,您必须告诉 JVM 不允许卸载类。这正是这样NewGlobalRef做的。您只需增加传递给的引用的引用,NewGlobalRef因此引用计数永远不会下降到零,并且不允许抓取收集来清理被引用的元素。

注意:创建 aNewGlobalRef有一个严重的缺点:除了在 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));
}

Sorry for this a bit lengthy posting but I hope this helps to solve your confusion a bit and gives you a bit an insight of the inner workings of JNI together with a little receipe how to deal with this efficiently.

Edit: You can find documentation about JNI calls on oracle's website

于 2013-02-21T14:27:08.877 回答