Maor Cohen
Maor Cohen

Reputation: 956

Android: Exception when calling a static java method from cpp thread

When my java class loads, I call this jni method to set "listener" from cpp to java (I record audio using cpp and want to pass its bytes to java) :

MyJava.class

setListener(JNIEnv *env, jclass thiz);

myCpp.cpp

setListener(JNIEnv *env, jclass thiz) {
    envMyClass = env;
    classMyClass = thiz;
    // I read that I need these 2 lines in order to connect the java thread to the cpp thread
    env->GetJavaVM(&javavm);
    GetJniEnv(javavm, &envCamera);
    return 0;
}


bool GetJniEnv(JavaVM *vm, JNIEnv **env) {
    bool did_attach_thread = false;

    *env = nullptr;
    // Check if the current thread is attached to the VM
    auto get_env_result = vm->GetEnv((void**)env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (vm->AttachCurrentThread(env, NULL) == JNI_OK) {
            did_attach_thread = true;
        } else {
            // Failed to attach thread. Throw an exception if you want to.
        }
    } else if (get_env_result == JNI_EVERSION) {
        // Unsupported JNI version. Throw an exception if you want to.
    }
    return did_attach_thread;
}

and the in myCpp.cpp thread I'm trying to call:

if (envMyClass != nullptr && classMyClass != nullptr && javavm != nullptr) {
    LOGD("000");
    jmethodID javaMethod = envMyClass->GetStaticMethodID(classMyClass, "myJavaFunction", "()V");
    LOGD("001");
    envMyClass->CallStaticVoidMethod(classMyClass, javaMethod);

}

and it crashes on the line of "jmethodId javaMethod.."

myJavaFunction is a method in MyJava class:

public static void myJavaFunction() {
    Log.d("my_log", "jni callback");
}

the crash:

   Abort message: 'JNI DETECTED ERROR IN APPLICATION: a thread (tid 12346 is making JNI calls without being attached
    in call to GetStaticMethodID'

Any idea how to fix it?

Upvotes: 0

Views: 143

Answers (1)

6d7a
6d7a

Reputation: 41

I've noticed a few things:

  1. In setListener, I assume you are assigning jclass thiz to a global variable classMyClass. To do so, you should use classMyClass = env->NewGlobalRef(thiz), otherwise the class reference will not be valid (reference).
  2. You are calling GetJniEnv but you do not check its result and therefore you do not actually know whether the current thread has attached successfully.
  3. I assume you are attaching the current thread in setListener, save the env to a global variable, and then use that variable at another point in your code. Could it be that you are switching thread context somewhere in between? You could do the following to make sure the current thread is really attached:
    JNIEnv *thisEnv;
    int getEnvStat = jvm->GetEnv((void **)&thisEnv, JNI_VERSION_1_6 /* or whatever your JNI version is*/);
    if (getEnvStat == JNI_EDETACHED)
    {
        if (jvm->AttachCurrentThread(&thisEnv, NULL) != 0)
        {
           // throw error
        }
        else
        {
           jmethodID javaMethod = thisEnv->GetStaticMethodID(classMyClass, "myJavaFunction", "()V");
           thisEnv->CallStaticVoidMethod(classMyClass, javaMethod);
        }
    }
    else if (getEnvStat == JNI_OK)
    {
       jmethodID javaMethod = thisEnv->GetStaticMethodID(classMyClass, "myJavaFunction", "()V");
       thisEnv->CallStaticVoidMethod(classMyClass, javaMethod);
    }
    else if (getEnvStat == JNI_EVERSION)
    {
       // throw error
    }
}
  1. You can make this more efficient if you call jmethodID javaMethod = thisEnv->GetStaticMethodID(classMyClass, "myJavaFunction", "()V"); only once initially and store the jmethodID in a global variable. jmethodIDs are valid across envs.

Upvotes: 1

Related Questions