Search code examples
javaandroid-ndkjava-native-interface

JNI callback to Java


Need some help in understanding when the java string is deleted. On the c side the string is deleted after the call to onCandidates and on the java side setRemoteDescription may take some time to process sdp. How does java sync up to the c side and would it be normal to make a copy of the string before proceeding.

C source code

jstring jstrBuf = (*env)->NewStringUTF(env, sdp);
(*env)->CallVoidMethod(env, onCandidatesCallbackObj, onCandidatesCallback, jstrBuf);
(*env)->DeleteLocalRef(env, jstrBuf);

java code

private void onCandidates(String sdp) {
  setRemoteDescription(sdp); // may take time?
}

My code works most of the time but some times my app crashes. Initially this was due to a different thread on the c side. I think my solution is working correctly so now I am considering the life time of the variable.

static void on_gathering_done(juice_agent_t *agent, void *user_ptr) {
    LOGI("on_gathering_done");

    char sdp[JUICE_MAX_SDP_STRING_LEN];
    juice_get_local_description(local_agent, sdp, JUICE_MAX_SDP_STRING_LEN);

    JNIEnv *env;
    jint res = (*java_vm)->GetEnv(java_vm, (void **) &env, JNI_VERSION_1_6);

    // is current thread known?
    if (res != JNI_OK) {
        LOGI("attaching current thread to VM");
        // not known, need to attach
        if ((*java_vm)->AttachCurrentThread(java_vm, &env, NULL) == JNI_OK) {

            jstring jstrBuf = (*env)->NewStringUTF(env, sdp);
            (*env)->CallVoidMethod(env, onCandidatesCallbackObj, onCandidatesCallback, jstrBuf);
            (*env)->DeleteLocalRef(env, jstrBuf);

            (*java_vm)->DetachCurrentThread(java_vm);
        }
    } else {
        LOGI("thread already attached to VM");

        jstring jstrBuf = (*env)->NewStringUTF(env, sdp);
        (*env)->CallVoidMethod(env, onCandidatesCallbackObj, onCandidatesCallback, jstrBuf);
        (*env)->DeleteLocalRef(env, jstrBuf);
    }
}

Thanks Robert


Solution

  • Let's go through your code line by line and think about memory management:

    jstring jstrBuf = (*env)->NewStringUTF(env, sdp);
    

    This line creates a Java String object on the heap by copying from sdp. It only has one reference, namely the JNI-local reference here in jstrBuf.

    (*env)->CallVoidMethod(env, onCandidatesCallbackObj, onCandidatesCallback, jstrBuf);
    

    This line passes jstrBuf to your Java method. Method parameters are garbage collection roots, so there is an extra reference to your object from the call stack.

    (*env)->DeleteLocalRef(env, jstrBuf);
    

    This line removes your JNI-held local reference.
    At this point either setRemoteDescription created an additional reference (eg by storing sdp somewhere or taking a substring of it, or it did not.

    In the second case jstrBuf will no longer have references to it and the memory for it will be freed when the garbage collector runs again.