Hi all,
I have been trying different approaches to dealing with a JNI issue with little success and I would like to find out if anyone has faced similar threading issues in this forum.
We are building an Android app with integrated Crashlytics to log our native crashes. Unfortunately when the issue I am writing about appears in our crashlytics webview, we are given only a single line describing the method in which the crash was caused. Ie. no stack trace. I will highlight this crashing method in detail below.
In order to call Java methods from the native code base, I am following this 'standard' procedure:
i) initialize JNI, and cache a global JavaVM* jvm:
env->GetJavaVM(&jvm);
obj = env->NewGlobalRef(object);
jclass cls = env->GetObjectClass(obj);
ii) all of the Java methods needed to be accessed by the native side live in a single java class, from where the initialization of the JNI is invoked, so the jclass cls is used to cache jmethodIDs as follows:
(example for one jmethodID)
someMethodToBeCalledJAVA_CLASSNAME = env->GetMethodID(cls, "method1Name", "()V");
iii) when this method is called from the C++ code, I determine whether
the current thread needs to be attached to the jvm, and call the method
as follows:
void methodName() {
env_forMethod1 = checkJNIEnvStatus(&thread_status[method1Idx]);
if (env_forMethod1 != NULL) {
env_forMethod->CallVoidMethod(obj,someMethodToBeCalledJAVA_CLASSNAME);
if (thread_status[method1Idx] == THREAD_CONNECTION_STATUS_SUCCESS) {
jvm->DetachCurrentThread();
} else {
//LOGD("thread already connected, will be detached elsewhere");
}
}
}
The goal is to ALWAYS try to attach the thread to the jvm before making the call, and to ALWAYS detach the thread. In the case that the thread was already connected, we do not detach the thread because we can assume that if for some reason we are sharing the thread with another method that has already attached itself to the jvm, then it will detach itself in the same procedure.
v) checkJNIStatus(int* status) {} is a method that will check the thread state, and save the value in an array called thread_status[], of size equal to the number of different C++ to Java methods we have in our program. Each index of the array corresponds to a different env_forMethodx, and are indexed with a human readable enum :
typedef enum JNI_BROADCAST_GROUPS {
method1Idx,
method2Idx,
method3Idx,
ETCIdx
} JNI_BROADCAST_GROUPS;It is within checkJNISatus() that we have our crash:
JNIEnv* checkJNIEnvStatus(int *status) {
JNIEnv* env;
int getEnvStatus = jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
if (getEnvStatus == JNI_EDETACHED) {
//LOGD("SBP Thread detached, must attach now");
if (jvm->AttachCurrentThread(&env,NULL) != THREAD_CONNECTION_STATUS_SUCCESS) {
LOGE("Failed to attach env for JNI");
status[0] = THREAD_CONNECTION_FAILURE;
return NULL;
} else {
status[0] = THREAD_CONNECTION_STATUS_SUCCESS;
return env;
//LOGD("Attach Success");
}
} else if (getEnvStatus == JNI_OK) {
//LOGD("Thread Already Attached");
status[0] = THREAD_ALREADY_CONNECTED;
return env;
} else if (getEnvStatus == JNI_EVERSION) {
LOGE("Unsupported JNI version");
status[0] = THREAD_CONNECTION_FAILURE;
return NULL; ;
} else {
// default
LOGE("Default Thread Management return null");
status[0] = THREAD_CONNECTION_STATUS_UNDEFINED;
return NULL;
}
}Unfortunately, we are only able to know that the crash happens within the checkJNIStatus() method. Furthermore the affects only about 2% of our users. Each method that invokes checkJNIStatus() is touched with much greater frequency than would lead to a conclusion that one single method call has a typo. Rather, the crash seems to be the result of some asynchronous threading problems.
The Crashlytics log is attached.
Any help would be fantastic. !!
Thanks,
JJ