Thursday, August 6, 2015

Android JNI and pthreads

Here is a quick gotcha for using pthreads that make JNI calls on Android: the JNIEnv is not shared between threads.

This simple implementation of threads will crash on any JNI call.
struct ThreadArgs
{
GHRunnable* mRunnable;
};

void* threadLaunch(void* arg)
{
ThreadArgs* threadArgs = (ThreadArgs*)arg;
threadArgs->mRunnable->run();
delete threadArgs;
}

void GHAndroidThread::runThread(GHRunnable& runnable)
{
ThreadArgs* threadArgs = new ThreadArgs;
threadArgs->mRunnable = &runnable;

pthread_t myPThread;
if (pthread_create(&myPThread, NULL, threadLaunch, (void*)(threadArgs)))
{
GHDebugMessage::outputString("Error creating pthread");
return;
}
}
 You need to call AttachCurrentThread and DetachCurrentThread to allow JNI access.
struct ThreadArgs
{
GHRunnable* mRunnable;
JavaVM* mJVM;
};

void* threadLaunch(void* arg)
{
ThreadArgs* threadArgs = (ThreadArgs*)arg;
JNIEnv* jniEnv;
bool attachNeeded = true;

int status = threadArgs->mJVM->GetEnv((void**)&jniEnv, JNI_VERSION_1_6);
if (status == JNI_OK)
{
attachNeeded = false;
}

if (attachNeeded)
{
threadArgs->mJVM->AttachCurrentThread(&jniEnv, NULL);
}

threadArgs->mRunnable->run();

if (attachNeeded)
{
threadArgs->mJVM->DetachCurrentThread();
}

delete threadArgs;
}

void GHAndroidThread::runThread(GHRunnable& runnable)
{
ThreadArgs* threadArgs = new ThreadArgs;
threadArgs->mRunnable = &runnable;
threadArgs->mJVM = mJNIMgr.getJVM();

pthread_t myPThread;
if (pthread_create(&myPThread, NULL, threadLaunch, (void*)(threadArgs)))
{
GHDebugMessage::outputString("Error creating pthread");
return;
}
}
 Lastly any JNI calls should probably include the following wrapper to get the correct env for the current thread.  I tried it without this code and didn't have problems but the docs do say it's a different JNIEnv per thread.  Maybe it would crash without this on a different Android version.

JNIEnv& GHJNIMgr::getJNIEnv(void)
{
JNIEnv* env;
int status = mJVM->GetEnv((void**)&env, JNI_VERSION_1_6);
if (status < 0) {
GHDebugMessage::outputString("Failed to get JNIEnv");
}
return *env;
}

No comments:

Post a Comment