Search code examples
javaandroidc++android-ndkjava-native-interface

Calling into a saved java object via JNI from a different thread


I have a java object which calls into a C++ shared object via JNI. In C++, I am saving a reference to the JNIEnv and jObject.

JavaVM * jvm;
JNIEnv * myEnv;
jobject myobj;

JNIEXPORT void JNICALL Java_org_api_init
   (JNIEnv *env, jobject jObj) {
    myEnv = env;
    myobj = jObj;
}

I also have a GLSurface renderer and it eventually calls the C++ shared object mentioned above on a different thread, the GLThread. I am then trying to call back into my original Java object using the jobject I saved initially, but I think because I am on the GLThread, I get the following error.

W/dalvikvm(16101): JNI WARNING: 0x41ded218 is not a valid JNI reference
I/dalvikvm(16101): "GLThread 981" prio=5 tid=15 RUNNABLE
I/dalvikvm(16101):   | group="main" sCount=0 dsCount=0 obj=0x41d6e220 self=0x5cb11078
I/dalvikvm(16101):   | sysTid=16133 nice=0 sched=0/0 cgrp=apps handle=1555429136
I/dalvikvm(16101):   | schedstat=( 0 0 0 ) utm=42 stm=32 core=1

The code calling back into Java :

void setData()
    {
        jvm->AttachCurrentThread(&myEnv, 0);

        jclass javaClass = myEnv->FindClass("com/myapp/myClass");
        if(javaClass == NULL){
            LOGD("ERROR - cant find class");
        }

        jmethodID method = myEnv->GetMethodID(javaClass, "updateDataModel", "()V");
        if(method == NULL){
            LOGD("ERROR - cant access method");
        }

        // this works, but its a new java object
        //jobject myobj2 = myEnv->NewObject(javaClass, method);

        //this is where the crash occurs
        myEnv->CallVoidMethod(myobj, method, NULL); 

}

If instead I create a new jObject using env->NewObject, I can succuessfully call back into Java, but it is a new object and I dont want that. I need to get back to my original Java Object.

Is it a matter of switching threads before I call back into Java? If so, how do I do so ?


Solution

  • Accessing object from different threads is fine. Problem is that JNI calls get objects as local references. If you want to keep reference to jobject between JNI calls you need to make it global reference:

    myobj = env->NewGlobalRef(jObj);
    

    Remember to free it after you're done using it otherwise garbage collector won't collect it and you'll get memory leaks:

    myEnv->DeleteGlobalRef(myobj);
    

    Read about global vs local references here.