Android 如何在循环中正确释放jstring?

Android 如何在循环中正确释放jstring?,android,android-ndk,java-native-interface,Android,Android Ndk,Java Native Interface,我的应用程序需要使用jni。逻辑如下: void myJniFunc(JNIEnv *env, jclass clazz, jobjectArray items) { int count = 10; struct MyObj *myObjArray = (struct MyObj*)malloc(sizeof(struct MyObj) * count); for (i = 0; i < count; i++) { jobject obj = (*e

我的应用程序需要使用jni。逻辑如下:

void myJniFunc(JNIEnv *env, jclass clazz, jobjectArray items) {
    int count = 10;
    struct MyObj *myObjArray = (struct MyObj*)malloc(sizeof(struct MyObj) * count);
    for (i = 0; i < count; i++) {
        jobject obj = (*env)->GetObjectArrayElement(env, items, i);
        jfieldID fieldId = ...;
        jstring jstr = (*env)->GetObjectField(env, obj, fieldId);
        myObjArray[i].name = (*env)->GetStringUTFChars(env, jstr);
        (*env)->DeleteLocalRef(env, obj);
        // Location A
    }

    // some code which will use myObjArray
    process(count, myObjectArray);

    // Location B
}
  • 如果在位置A释放返回的数组,则myObjArray.name将为空
  • 如果我在位置B释放返回的数组,因为我将保留jstring的引用,那么“添加到JNI本地引用表失败(有512个条目)”将发生
  • 我的问题是:
    如果我想正确地释放jstring,我应该怎么做?

    经过一些实验,我得到了一个答案,但我不确定它是否完全正确。 我在循环中删除了jstring的引用,错误“添加到JNI本地引用表失败(有512个条目)”不再发生

    void myJniFunc(JNIEnv *env, jclass clazz, jobjectArray items) {
        int count = 10;
        jstring tempArray[count];
        struct MyObj *myObjArray = (struct MyObj*)malloc(sizeof(struct MyObj) * count);
        for (i = 0; i < count; i++) {
            jobject obj = (*env)->GetObjectArrayElement(env, items, i);
            jfieldID fieldId = ...;
            jstring jstr = (*env)->GetObjectField(env, obj, fieldId);
            myObjArray[i].name = (*env)->GetStringUTFChars(env, jstr);
            (*env)->DeleteLocalRef(env, obj);
    
            // Location A
            tempArray[i] = jstr;
            (*env)->DeleteLocalRef(jstr);
        }
    
        // some code which will use myObjArray
        process(count, myObjectArray);
    
        // Location B
        for (i = 0; i < count; i++) {
            (*env)->ReleaseStringUTFChars(env, tempArray[i], myObjectArray[i].name);
        }
    }
    
    void myJniFunc(JNIEnv*env、jclass clazz、jobjectArray项){
    整数计数=10;
    jstring tempArray[count];
    struct MyObj*myObjArray=(struct MyObj*)malloc(sizeof(struct MyObj)*count);
    对于(i=0;iGetObjectArrayElement(env,items,i);
    jfieldID fieldId=。。。;
    jstringjstr=(*env)->GetObjectField(env,obj,fieldId);
    myObjArray[i].name=(*env)->GetStringUTFChars(env,jstr);
    (*env)->DeleteLocalRef(env,obj);
    //地点A
    tempArray[i]=jstr;
    (*env)->DeleteLocalRef(jstr);
    }
    //一些将使用myObjArray的代码
    进程(计数,myObjectArray);
    //地点B
    对于(i=0;iReleaseStringUTFChars(env,tempArray[i],myObjectArray[i].name);
    }
    }
    
    我关注的是: 函数“ReleaseStringUTFChars”的第二个参数需要是一个jstring。 所以我创建了一个数组来保存jstring的引用,以供以后发布。 当我在循环中删除jstring的引用时,这意味着jstring被释放。 在这个jstring上有我称之为“ReleaseStringUTFChars”的问题吗?
    通过测试,我没有遇到任何问题。

    因为您的循环正在创建本地引用(GetObjectField),所以需要在循环中释放它(DeleteLocalRef),否则您将遇到本地引用的限制。您必须完全处理两次调用之间的Java字符串

    由于您希望将字符串的字节保留在循环之外使用,因此需要复制字节,因为在释放字符串引用之前必须释放JVM的固定(或临时副本)(GetStringUTFChars)(ReleaseStringUTFChars)

    因此,循环中字符串的顺序必须是:

  • GetObjectField
  • GetStringUTFChars
  • 自己复制
  • ReleaseStringUTFChars
  • DeleteLocalRef

  • 注意:使用
    GetStringUTFChars
    可以获得一个指向Java字符串的修改UTF-8编码的指针。这里有两点:

  • 您的代码应该能够处理修改的UTF-8编码字符。(它每个字符有一到六个字节,并以一种特殊的方式对NUL进行编码。)
  • 文档没有说明数组是否以0结尾。您可以使用
    GetStringUTFLength
    获取修改后的UTF-8编码中的字节数,不计算任何0-终止符。(各种JNI实现都同意阵列是0端接的。)如果您想使用终止符创建自己的副本,请确保为终止符添加空间
  • 如果您希望使用UTF-16编码,请使用
    GetStringChars
    GetStringLength
    。在这种情况下,阵列肯定不会终止;它使用内部计数和字符串字节,而不进行任何转换


    或者,如果您想更改字符集,例如,真正的“UTF-8”、“ASCII”、“CP437”或“Windows-1252”或您的代码可以处理的其他内容,请使用
    字符串.getBytes
    重载或
    字符集
    类。如果您想控制如何处理目标字符集中不支持的字符,请使用
    Charset
    类。

    除了Tom Blodget的帖子之外,我可以添加:不要对jstring对象使用DeleteLocalRef方法,并且不要在第一位(例如,当您从java方法检索字符串时)!将其保存在jobject类型引用中,并且仅当您将其作为param传递时才将其转换为jstring,否则会出现“JNI错误(app bug):accessed stale Local”错误! 例如,不要做这样的事情:

    jstring imeiObj = (jstring)env->CallObjectMethod(telephonyManagerObj, jniInfo->jniCache.telephonyManagerClass_getDeviceIdMethod, i);
    const char* imeiStr = env->GetStringUTFChars(imeiObj, &isCopy);
    env->DeleteLocalRef((jobject)imeiObj);
    
    最好是:

    jobject imeiObj = env->CallObjectMethod(telephonyManagerObj, jniInfo->jniCache.telephonyManagerClass_getDeviceIdMethod, i);
    const char* imeiStr = env->GetStringUTFChars((jstring)imeiObj, &isCopy);
    env->DeleteLocalRef(imeiObj);
    

    我不知道为什么会这样

    MyObj的“name”字段实际上应该包含一个
    jstring
    ,还是应该包含一个C
    char*
    ?似乎是后者,在这种情况下,您应该从每个字符串中获取字符,并将它们复制到新分配的
    char
    数组中,然后将它们存储在
    data
    字段中,然后释放Java字符。@Ernest Friedman Hill抱歉,我忘记添加一行调用“GetStringUTFChars”。我再次编辑这个问题。谢谢。这可能就是所谓的“未定义行为”。对字符串的本地引用已被释放,但字符串仍被固定(或已为副本分配内存),然后使用本地引用执行另一个操作。删除后不要使用本地引用。我希望CheckJNI模式拒绝这个调用。当前实现在发布调用中没有使用jstring引用,但这可能会改变。MUTF-8字符串以null结尾。@fadden谢谢。为了保持实用性,我对措辞做了一些修改。
    jobject imeiObj = env->CallObjectMethod(telephonyManagerObj, jniInfo->jniCache.telephonyManagerClass_getDeviceIdMethod, i);
    const char* imeiStr = env->GetStringUTFChars((jstring)imeiObj, &isCopy);
    env->DeleteLocalRef(imeiObj);