JNIEnv全局引用与C中的jobject有何不同?

时间:2013-02-21 13:50:50

标签: android c java-native-interface jnienv

到目前为止,我已将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 tutorialthis pagethe oracle docs没有关于NewGlobalRef方法本身的任何信息。

修改

jmethodID changeID;
jmethodID getID;
jmethodID putID;
jmethodID flushID;
jmethodID delayID;
jobject myObject;
jclass    bluetoothClass;
JNIEnv *myEnv;

1 个答案:

答案 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通话的文档