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

标签 android c java-native-interface jnienv

到目前为止,我已经将 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/

相关文章:

c - 有没有办法检查是否在堆栈/堆上创建了可变长度数组?

c - 从 2 位分支预测器到 8 位预测器

java - 异常在线程 "main"java.lang.UnsatisfiedLinkError : no opencv_java249 in java. library.path

Java:将不同类型的数组相互排序

android - onSharedPreferenceChanged() 从未在 clear() 之后调用

c++ - c 和 c++ 中二维数组元素的类型是什么?

java - 如何为一组 Pascal 库函数编写 Java JNI 包装器?

java - C++的out参数对象创建和JNA

java - 如何实现可折叠(带 handle )的垂直侧边栏android

android - Galaxy Nexus 是小型、普通、大型还是超大型设备?