Fixes #1422, removes global JNI Env, uses per thread copies, adds thread auto detaching.
authorGabriel Jacobo <gabomdq@gmail.com>
Mon, 09 Jul 2012 18:08:06 -0300
changeset 635417840f487124
parent 6352 a9bcd26e7105
child 6355 3ef4d0e923cb
Fixes #1422, removes global JNI Env, uses per thread copies, adds thread auto detaching.
README.android
src/core/android/SDL_android.cpp
src/core/android/SDL_android.h
src/main/android/SDL_android_main.cpp
src/thread/pthread/SDL_systhread.c
     1.1 --- a/README.android	Thu Jul 05 12:16:44 2012 -0400
     1.2 +++ b/README.android	Mon Jul 09 18:08:06 2012 -0300
     1.3 @@ -92,6 +92,20 @@
     1.4  under iOS, if the OS can not restore your GL context it will just kill your app)
     1.5  
     1.6  ================================================================================
     1.7 + Threads and the JAVA VM
     1.8 +================================================================================
     1.9 +
    1.10 +For a quick tour on how Linux native threads interoperate with the JAVA VM, take
    1.11 +a look here: http://developer.android.com/guide/practices/jni.html
    1.12 +If you want to use threads in your SDL app, it's strongly recommended that you
    1.13 +do so by creating them using SDL functions. This way, the required attach/detach
    1.14 +handling is managed by SDL automagically. If you have threads created by other
    1.15 +means and they make calls to SDL functions, make sure that you call
    1.16 +Android_JNI_SetupThread before doing anything else otherwise SDL will attach
    1.17 +your thread automatically anyway (when you make an SDL call), but it'll never
    1.18 +detach it.
    1.19 +
    1.20 +================================================================================
    1.21   Additional documentation
    1.22  ================================================================================
    1.23  
     2.1 --- a/src/core/android/SDL_android.cpp	Thu Jul 05 12:16:44 2012 -0400
     2.2 +++ b/src/core/android/SDL_android.cpp	Mon Jul 09 18:08:06 2012 -0300
     2.3 @@ -33,6 +33,7 @@
     2.4  #include "../../video/android/SDL_androidvideo.h"
     2.5  
     2.6  #include <android/log.h>
     2.7 +#include <pthread.h>
     2.8  #define LOG_TAG "SDL_android"
     2.9  //#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    2.10  //#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    2.11 @@ -54,8 +55,7 @@
    2.12  /*******************************************************************************
    2.13                                 Globals
    2.14  *******************************************************************************/
    2.15 -static JNIEnv* mEnv = NULL;
    2.16 -static JNIEnv* mAudioEnv = NULL;
    2.17 +static pthread_key_t mThreadKey;
    2.18  static JavaVM* mJavaVM;
    2.19  
    2.20  // Main activity
    2.21 @@ -87,17 +87,28 @@
    2.22          LOGE("Failed to get the environment using GetEnv()");
    2.23          return -1;
    2.24      }
    2.25 +    /*
    2.26 +     * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
    2.27 +     * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
    2.28 +     */
    2.29 +    if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
    2.30 +        __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
    2.31 +    }
    2.32 +    else {
    2.33 +        Android_JNI_SetupThread();
    2.34 +    }
    2.35  
    2.36      return JNI_VERSION_1_4;
    2.37  }
    2.38  
    2.39  // Called before SDL_main() to initialize JNI bindings
    2.40 -extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
    2.41 +extern "C" void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
    2.42  {
    2.43      __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
    2.44  
    2.45 -    mEnv = env;
    2.46 -    mActivityClass = (jclass)env->NewGlobalRef(cls);
    2.47 +    Android_JNI_SetupThread();
    2.48 +
    2.49 +    mActivityClass = (jclass)mEnv->NewGlobalRef(cls);
    2.50  
    2.51      midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
    2.52                                  "createGLContext","(II)Z");
    2.53 @@ -202,7 +213,7 @@
    2.54                                      JNIEnv* env, jclass cls)
    2.55  {
    2.56      /* This is the audio thread, with a different environment */
    2.57 -    mAudioEnv = env;
    2.58 +    Android_JNI_SetupThread();
    2.59  
    2.60      Android_RunAudioThread();
    2.61  }
    2.62 @@ -248,6 +259,7 @@
    2.63  
    2.64  extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
    2.65  {
    2.66 +    JNIEnv *mEnv = Android_JNI_GetEnv();
    2.67      if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
    2.68          return SDL_TRUE;
    2.69      } else {
    2.70 @@ -257,13 +269,14 @@
    2.71  
    2.72  extern "C" void Android_JNI_SwapWindow()
    2.73  {
    2.74 +    JNIEnv *mEnv = Android_JNI_GetEnv();
    2.75      mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
    2.76  }
    2.77  
    2.78  extern "C" void Android_JNI_SetActivityTitle(const char *title)
    2.79  {
    2.80      jmethodID mid;
    2.81 -
    2.82 +    JNIEnv *mEnv = Android_JNI_GetEnv();
    2.83      mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
    2.84      if (mid) {
    2.85          jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
    2.86 @@ -288,6 +301,53 @@
    2.87      return retval;
    2.88  }
    2.89  
    2.90 +static void Android_JNI_ThreadDestroyed(void* value) {
    2.91 +    /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
    2.92 +    JNIEnv *env = (JNIEnv*) value;
    2.93 +    if (env != NULL) {
    2.94 +        mJavaVM->DetachCurrentThread();
    2.95 +        pthread_setspecific(mThreadKey, NULL);
    2.96 +    }
    2.97 +}
    2.98 +
    2.99 +JNIEnv* Android_JNI_GetEnv(void) {
   2.100 +    /* From http://developer.android.com/guide/practices/jni.html
   2.101 +     * All threads are Linux threads, scheduled by the kernel.
   2.102 +     * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   2.103 +     * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   2.104 +     * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   2.105 +     * and cannot make JNI calls.
   2.106 +     * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   2.107 +     * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   2.108 +     * is a no-op.
   2.109 +     * Note: You can call this function any number of times for the same thread, there's no harm in it
   2.110 +     */
   2.111 +
   2.112 +    JNIEnv *env;
   2.113 +    int status = mJavaVM->AttachCurrentThread(&env, NULL);
   2.114 +    if(status < 0) {
   2.115 +        LOGE("failed to attach current thread");
   2.116 +        return 0;
   2.117 +    }
   2.118 +
   2.119 +    return env;
   2.120 +}
   2.121 +
   2.122 +int Android_JNI_SetupThread(void) {
   2.123 +    /* From http://developer.android.com/guide/practices/jni.html
   2.124 +     * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   2.125 +     * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   2.126 +     * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   2.127 +     * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   2.128 +     * Note: The destructor is not called unless the stored value is != NULL
   2.129 +     * Note: You can call this function any number of times for the same thread, there's no harm in it
   2.130 +     *       (except for some lost CPU cycles)
   2.131 +     */
   2.132 +    JNIEnv *env = Android_JNI_GetEnv();
   2.133 +    pthread_setspecific(mThreadKey, (void*) env);
   2.134 +    return 1;
   2.135 +}
   2.136 +
   2.137  //
   2.138  // Audio support
   2.139  //
   2.140 @@ -301,18 +361,12 @@
   2.141      int audioBufferFrames;
   2.142  
   2.143      int status;
   2.144 -    JNIEnv *env;
   2.145 -    static bool isAttached = false;    
   2.146 -    status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   2.147 -    if(status < 0) {
   2.148 -        LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   2.149 -        status = mJavaVM->AttachCurrentThread(&env, NULL);
   2.150 -        if(status < 0) {
   2.151 -            LOGE("callback_handler: failed to attach current thread");
   2.152 -            return 0;
   2.153 -        }
   2.154 -        isAttached = true;
   2.155 +    JNIEnv *env = Android_JNI_GetEnv();
   2.156 +
   2.157 +    if (!env) {
   2.158 +        LOGE("callback_handler: failed to attach current thread");
   2.159      }
   2.160 +    Android_JNI_SetupThread();
   2.161  
   2.162      
   2.163      __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   2.164 @@ -339,10 +393,6 @@
   2.165          audioBufferFrames /= 2;
   2.166      }
   2.167   
   2.168 -    if (isAttached) {
   2.169 -        mJavaVM->DetachCurrentThread();
   2.170 -    }
   2.171 -
   2.172      return audioBufferFrames;
   2.173  }
   2.174  
   2.175 @@ -353,6 +403,8 @@
   2.176  
   2.177  extern "C" void Android_JNI_WriteAudioBuffer()
   2.178  {
   2.179 +    JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   2.180 +
   2.181      if (audioBuffer16Bit) {
   2.182          mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   2.183          mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   2.184 @@ -367,18 +419,7 @@
   2.185  extern "C" void Android_JNI_CloseAudioDevice()
   2.186  {
   2.187      int status;
   2.188 -    JNIEnv *env;
   2.189 -    static bool isAttached = false;    
   2.190 -    status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
   2.191 -    if(status < 0) {
   2.192 -        LOGE("callback_handler: failed to get JNI environment, assuming native thread");
   2.193 -        status = mJavaVM->AttachCurrentThread(&env, NULL);
   2.194 -        if(status < 0) {
   2.195 -            LOGE("callback_handler: failed to attach current thread");
   2.196 -            return;
   2.197 -        }
   2.198 -        isAttached = true;
   2.199 -    }
   2.200 +    JNIEnv *env = Android_JNI_GetEnv();
   2.201  
   2.202      env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   2.203  
   2.204 @@ -387,16 +428,13 @@
   2.205          audioBuffer = NULL;
   2.206          audioBufferPinned = NULL;
   2.207      }
   2.208 -
   2.209 -    if (isAttached) {
   2.210 -        mJavaVM->DetachCurrentThread();
   2.211 -    }
   2.212  }
   2.213  
   2.214  // Test for an exception and call SDL_SetError with its detail if one occurs
   2.215  static bool Android_JNI_ExceptionOccurred()
   2.216  {
   2.217      SDL_assert(LocalReferenceHolder::IsActive());
   2.218 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   2.219  
   2.220      jthrowable exception = mEnv->ExceptionOccurred();
   2.221      if (exception != NULL) {
   2.222 @@ -445,6 +483,7 @@
   2.223      jobject readableByteChannel;
   2.224      jstring fileNameJString;
   2.225  
   2.226 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   2.227      if (!refs.init(mEnv)) {
   2.228          goto failure;
   2.229      }
   2.230 @@ -529,6 +568,7 @@
   2.231          const char* fileName, const char*)
   2.232  {
   2.233      LocalReferenceHolder refs;
   2.234 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   2.235  
   2.236      if (!refs.init(mEnv)) {
   2.237          return -1;
   2.238 @@ -554,6 +594,7 @@
   2.239      int bytesRemaining = size * maxnum;
   2.240      int bytesRead = 0;
   2.241  
   2.242 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   2.243      if (!refs.init(mEnv)) {
   2.244          return -1;
   2.245      }
   2.246 @@ -593,6 +634,7 @@
   2.247  {
   2.248      LocalReferenceHolder refs;
   2.249      int result = 0;
   2.250 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   2.251  
   2.252      if (!refs.init(mEnv)) {
   2.253          SDL_SetError("Failed to allocate enough JVM local references");
     3.1 --- a/src/core/android/SDL_android.h	Thu Jul 05 12:16:44 2012 -0400
     3.2 +++ b/src/core/android/SDL_android.h	Mon Jul 09 18:08:06 2012 -0300
     3.3 @@ -47,6 +47,12 @@
     3.4  size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer, size_t size, size_t num);
     3.5  int Android_JNI_FileClose(SDL_RWops* ctx);
     3.6  
     3.7 +// Threads
     3.8 +#include <jni.h>
     3.9 +static void Android_JNI_ThreadDestroyed(void*);
    3.10 +JNIEnv *Android_JNI_GetEnv(void);
    3.11 +int Android_JNI_SetupThread(void);
    3.12 +
    3.13  /* Ends C function definitions when using C++ */
    3.14  #ifdef __cplusplus
    3.15  /* *INDENT-OFF* */
     4.1 --- a/src/main/android/SDL_android_main.cpp	Thu Jul 05 12:16:44 2012 -0400
     4.2 +++ b/src/main/android/SDL_android_main.cpp	Mon Jul 09 18:08:06 2012 -0300
     4.3 @@ -14,12 +14,6 @@
     4.4  // Called before SDL_main() to initialize JNI bindings in SDL library
     4.5  extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls);
     4.6  
     4.7 -// Library init
     4.8 -extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
     4.9 -{
    4.10 -    return JNI_VERSION_1_4;
    4.11 -}
    4.12 -
    4.13  // Start up the SDL app
    4.14  extern "C" void Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj)
    4.15  {
     5.1 --- a/src/thread/pthread/SDL_systhread.c	Thu Jul 05 12:16:44 2012 -0400
     5.2 +++ b/src/thread/pthread/SDL_systhread.c	Mon Jul 09 18:08:06 2012 -0300
     5.3 @@ -40,6 +40,9 @@
     5.4  #include "SDL_thread.h"
     5.5  #include "../SDL_thread_c.h"
     5.6  #include "../SDL_systhread.h"
     5.7 +#ifdef __ANDROID__
     5.8 +#include "../../core/android/SDL_android.h"
     5.9 +#endif
    5.10  
    5.11  /* List of signals to mask in the subthreads */
    5.12  static const int sig_list[] = {
    5.13 @@ -51,6 +54,9 @@
    5.14  static void *
    5.15  RunThread(void *data)
    5.16  {
    5.17 +#ifdef __ANDROID__
    5.18 +    Android_JNI_SetupThread();
    5.19 +#endif
    5.20      SDL_RunThread(data);
    5.21      return NULL;
    5.22  }