src/core/android/SDL_android.cpp
changeset 6354 17840f487124
parent 6335 fbb84f5b985f
child 6377 3d868ca4782f
     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");