1.1 --- a/src/core/android/SDL_android.cpp Thu Jul 05 12:16:44 2012 -0400
1.2 +++ b/src/core/android/SDL_android.cpp Mon Jul 09 18:08:06 2012 -0300
1.3 @@ -33,6 +33,7 @@
1.4 #include "../../video/android/SDL_androidvideo.h"
1.5
1.6 #include <android/log.h>
1.7 +#include <pthread.h>
1.8 #define LOG_TAG "SDL_android"
1.9 //#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
1.10 //#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
1.11 @@ -54,8 +55,7 @@
1.12 /*******************************************************************************
1.13 Globals
1.14 *******************************************************************************/
1.15 -static JNIEnv* mEnv = NULL;
1.16 -static JNIEnv* mAudioEnv = NULL;
1.17 +static pthread_key_t mThreadKey;
1.18 static JavaVM* mJavaVM;
1.19
1.20 // Main activity
1.21 @@ -87,17 +87,28 @@
1.22 LOGE("Failed to get the environment using GetEnv()");
1.23 return -1;
1.24 }
1.25 + /*
1.26 + * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
1.27 + * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
1.28 + */
1.29 + if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
1.30 + __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
1.31 + }
1.32 + else {
1.33 + Android_JNI_SetupThread();
1.34 + }
1.35
1.36 return JNI_VERSION_1_4;
1.37 }
1.38
1.39 // Called before SDL_main() to initialize JNI bindings
1.40 -extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
1.41 +extern "C" void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
1.42 {
1.43 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
1.44
1.45 - mEnv = env;
1.46 - mActivityClass = (jclass)env->NewGlobalRef(cls);
1.47 + Android_JNI_SetupThread();
1.48 +
1.49 + mActivityClass = (jclass)mEnv->NewGlobalRef(cls);
1.50
1.51 midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
1.52 "createGLContext","(II)Z");
1.53 @@ -202,7 +213,7 @@
1.54 JNIEnv* env, jclass cls)
1.55 {
1.56 /* This is the audio thread, with a different environment */
1.57 - mAudioEnv = env;
1.58 + Android_JNI_SetupThread();
1.59
1.60 Android_RunAudioThread();
1.61 }
1.62 @@ -248,6 +259,7 @@
1.63
1.64 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
1.65 {
1.66 + JNIEnv *mEnv = Android_JNI_GetEnv();
1.67 if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
1.68 return SDL_TRUE;
1.69 } else {
1.70 @@ -257,13 +269,14 @@
1.71
1.72 extern "C" void Android_JNI_SwapWindow()
1.73 {
1.74 + JNIEnv *mEnv = Android_JNI_GetEnv();
1.75 mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers);
1.76 }
1.77
1.78 extern "C" void Android_JNI_SetActivityTitle(const char *title)
1.79 {
1.80 jmethodID mid;
1.81 -
1.82 + JNIEnv *mEnv = Android_JNI_GetEnv();
1.83 mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
1.84 if (mid) {
1.85 jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
1.86 @@ -288,6 +301,53 @@
1.87 return retval;
1.88 }
1.89
1.90 +static void Android_JNI_ThreadDestroyed(void* value) {
1.91 + /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
1.92 + JNIEnv *env = (JNIEnv*) value;
1.93 + if (env != NULL) {
1.94 + mJavaVM->DetachCurrentThread();
1.95 + pthread_setspecific(mThreadKey, NULL);
1.96 + }
1.97 +}
1.98 +
1.99 +JNIEnv* Android_JNI_GetEnv(void) {
1.100 + /* From http://developer.android.com/guide/practices/jni.html
1.101 + * All threads are Linux threads, scheduled by the kernel.
1.102 + * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
1.103 + * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
1.104 + * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
1.105 + * and cannot make JNI calls.
1.106 + * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
1.107 + * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
1.108 + * is a no-op.
1.109 + * Note: You can call this function any number of times for the same thread, there's no harm in it
1.110 + */
1.111 +
1.112 + JNIEnv *env;
1.113 + int status = mJavaVM->AttachCurrentThread(&env, NULL);
1.114 + if(status < 0) {
1.115 + LOGE("failed to attach current thread");
1.116 + return 0;
1.117 + }
1.118 +
1.119 + return env;
1.120 +}
1.121 +
1.122 +int Android_JNI_SetupThread(void) {
1.123 + /* From http://developer.android.com/guide/practices/jni.html
1.124 + * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
1.125 + * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
1.126 + * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
1.127 + * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
1.128 + * Note: The destructor is not called unless the stored value is != NULL
1.129 + * Note: You can call this function any number of times for the same thread, there's no harm in it
1.130 + * (except for some lost CPU cycles)
1.131 + */
1.132 + JNIEnv *env = Android_JNI_GetEnv();
1.133 + pthread_setspecific(mThreadKey, (void*) env);
1.134 + return 1;
1.135 +}
1.136 +
1.137 //
1.138 // Audio support
1.139 //
1.140 @@ -301,18 +361,12 @@
1.141 int audioBufferFrames;
1.142
1.143 int status;
1.144 - JNIEnv *env;
1.145 - static bool isAttached = false;
1.146 - status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
1.147 - if(status < 0) {
1.148 - LOGE("callback_handler: failed to get JNI environment, assuming native thread");
1.149 - status = mJavaVM->AttachCurrentThread(&env, NULL);
1.150 - if(status < 0) {
1.151 - LOGE("callback_handler: failed to attach current thread");
1.152 - return 0;
1.153 - }
1.154 - isAttached = true;
1.155 + JNIEnv *env = Android_JNI_GetEnv();
1.156 +
1.157 + if (!env) {
1.158 + LOGE("callback_handler: failed to attach current thread");
1.159 }
1.160 + Android_JNI_SetupThread();
1.161
1.162
1.163 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
1.164 @@ -339,10 +393,6 @@
1.165 audioBufferFrames /= 2;
1.166 }
1.167
1.168 - if (isAttached) {
1.169 - mJavaVM->DetachCurrentThread();
1.170 - }
1.171 -
1.172 return audioBufferFrames;
1.173 }
1.174
1.175 @@ -353,6 +403,8 @@
1.176
1.177 extern "C" void Android_JNI_WriteAudioBuffer()
1.178 {
1.179 + JNIEnv *mAudioEnv = Android_JNI_GetEnv();
1.180 +
1.181 if (audioBuffer16Bit) {
1.182 mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
1.183 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
1.184 @@ -367,18 +419,7 @@
1.185 extern "C" void Android_JNI_CloseAudioDevice()
1.186 {
1.187 int status;
1.188 - JNIEnv *env;
1.189 - static bool isAttached = false;
1.190 - status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
1.191 - if(status < 0) {
1.192 - LOGE("callback_handler: failed to get JNI environment, assuming native thread");
1.193 - status = mJavaVM->AttachCurrentThread(&env, NULL);
1.194 - if(status < 0) {
1.195 - LOGE("callback_handler: failed to attach current thread");
1.196 - return;
1.197 - }
1.198 - isAttached = true;
1.199 - }
1.200 + JNIEnv *env = Android_JNI_GetEnv();
1.201
1.202 env->CallStaticVoidMethod(mActivityClass, midAudioQuit);
1.203
1.204 @@ -387,16 +428,13 @@
1.205 audioBuffer = NULL;
1.206 audioBufferPinned = NULL;
1.207 }
1.208 -
1.209 - if (isAttached) {
1.210 - mJavaVM->DetachCurrentThread();
1.211 - }
1.212 }
1.213
1.214 // Test for an exception and call SDL_SetError with its detail if one occurs
1.215 static bool Android_JNI_ExceptionOccurred()
1.216 {
1.217 SDL_assert(LocalReferenceHolder::IsActive());
1.218 + JNIEnv *mEnv = Android_JNI_GetEnv();
1.219
1.220 jthrowable exception = mEnv->ExceptionOccurred();
1.221 if (exception != NULL) {
1.222 @@ -445,6 +483,7 @@
1.223 jobject readableByteChannel;
1.224 jstring fileNameJString;
1.225
1.226 + JNIEnv *mEnv = Android_JNI_GetEnv();
1.227 if (!refs.init(mEnv)) {
1.228 goto failure;
1.229 }
1.230 @@ -529,6 +568,7 @@
1.231 const char* fileName, const char*)
1.232 {
1.233 LocalReferenceHolder refs;
1.234 + JNIEnv *mEnv = Android_JNI_GetEnv();
1.235
1.236 if (!refs.init(mEnv)) {
1.237 return -1;
1.238 @@ -554,6 +594,7 @@
1.239 int bytesRemaining = size * maxnum;
1.240 int bytesRead = 0;
1.241
1.242 + JNIEnv *mEnv = Android_JNI_GetEnv();
1.243 if (!refs.init(mEnv)) {
1.244 return -1;
1.245 }
1.246 @@ -593,6 +634,7 @@
1.247 {
1.248 LocalReferenceHolder refs;
1.249 int result = 0;
1.250 + JNIEnv *mEnv = Android_JNI_GetEnv();
1.251
1.252 if (!refs.init(mEnv)) {
1.253 SDL_SetError("Failed to allocate enough JVM local references");