Browse Source

android: properly support multiple Java threads

rdb 7 years ago
parent
commit
c1fccd311b

+ 4 - 0
panda/src/android/PandaActivity.java

@@ -41,6 +41,10 @@ public class PandaActivity extends NativeActivity {
         return BitmapFactory.decodeStream(stream, null, options);
     }
 
+    protected static String getCurrentThreadName() {
+        return Thread.currentThread().getName();
+    }
+
     static {
         System.loadLibrary("gnustl_shared");
         System.loadLibrary("p3dtool");

+ 29 - 5
panda/src/android/android_main.cxx

@@ -16,6 +16,7 @@
 #include "virtualFileMountAndroidAsset.h"
 #include "virtualFileSystem.h"
 #include "filename.h"
+#include "thread.h"
 
 #include "config_display.h"
 // #define OPENGLES_1 #include "config_androiddisplay.h"
@@ -29,21 +30,44 @@ extern int main(int argc, char **argv);
 /**
  * This function is called by native_app_glue to initialize the program.  It
  * simply stores the android_app object and calls main() normally.
+ *
+ * Note that this does not run in the main thread, but in a thread created
+ * specifically for this activity by android_native_app_glue.
  */
 void android_main(struct android_app* app) {
   panda_android_app = app;
 
-  // Attach the current thread to the JVM.
+  // Attach the app thread to the Java VM.
   JNIEnv *env;
   ANativeActivity* activity = app->activity;
-  int status = activity->vm->AttachCurrentThread(&env, NULL);
-  if (status < 0 || env == NULL) {
+  int status = activity->vm->AttachCurrentThread(&env, nullptr);
+  if (status < 0 || env == nullptr) {
     android_cat.error() << "Failed to attach thread to JVM!\n";
     return;
   }
 
-  // Fetch the data directory.
   jclass activity_class = env->GetObjectClass(activity->clazz);
+
+  // Get the current Java thread name.  This just helps with debugging.
+  jmethodID methodID = env->GetStaticMethodID(activity_class, "getCurrentThreadName", "()Ljava/lang/String;");
+  jstring jthread_name = (jstring) env->CallStaticObjectMethod(activity_class, methodID);
+
+  string thread_name;
+  if (jthread_name != nullptr) {
+    const char *c_str = env->GetStringUTFChars(jthread_name, nullptr);
+    thread_name.assign(c_str);
+    env->ReleaseStringUTFChars(jthread_name, c_str);
+  }
+
+  // Before we make any Panda calls, we must make the thread known to Panda.
+  // This will also cause the JNIEnv pointer to be stored on the thread.
+  // Note that we must keep a reference to this thread around.
+  PT(Thread) current_thread = Thread::bind_thread(thread_name, "android_app");
+
+  android_cat.info()
+    << "New native activity started on " << *current_thread << "\n";
+
+  // Fetch the data directory.
   jmethodID get_appinfo = env->GetMethodID(activity_class, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
 
   jobject appinfo = env->CallObjectMethod(activity->clazz, get_appinfo);
@@ -77,7 +101,7 @@ void android_main(struct android_app* app) {
   }
 
   // Get the path to the APK.
-  jmethodID methodID = env->GetMethodID(activity_class, "getPackageCodePath", "()Ljava/lang/String;");
+  methodID = env->GetMethodID(activity_class, "getPackageCodePath", "()Ljava/lang/String;");
   jstring code_path = (jstring) env->CallObjectMethod(activity->clazz, methodID);
 
   const char* apk_path;

+ 6 - 3
panda/src/android/config_android.cxx

@@ -49,8 +49,9 @@ init_libandroid() {
 jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
   init_libandroid();
 
-  JNIEnv *env = get_jni_env();
-  assert(env != NULL);
+  Thread *thread = Thread::get_current_thread();
+  JNIEnv *env = thread->get_jni_env();
+  nassertr(env != nullptr, -1);
 
   jni_PandaActivity = env->FindClass("org/panda3d/android/PandaActivity");
   jni_PandaActivity = (jclass) env->NewGlobalRef(jni_PandaActivity);
@@ -75,7 +76,9 @@ jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
  * references.
  */
 void JNI_OnUnload(JavaVM *jvm, void *reserved) {
-  JNIEnv *env = get_jni_env();
+  Thread *thread = Thread::get_current_thread();
+  JNIEnv *env = thread->get_jni_env();
+  nassertv(env != nullptr);
 
   env->DeleteGlobalRef(jni_PandaActivity);
   env->DeleteGlobalRef(jni_BitmapFactory_Options);

+ 8 - 1
panda/src/android/pnmFileTypeAndroidReader.cxx

@@ -76,7 +76,14 @@ Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
   }
 
   streampos pos = _file->tellg();
-  _env = get_jni_env();
+
+  Thread *current_thread = Thread::get_current_thread();
+  _env = current_thread->get_jni_env();
+  nassertd(_env != nullptr) {
+    _is_valid = false;
+    return;
+  }
+
   jobject opts = _env->CallStaticObjectMethod(jni_PandaActivity,
                                               jni_PandaActivity_readBitmapSize,
                                               (jlong) _file);

+ 0 - 37
panda/src/express/config_express.cxx

@@ -194,40 +194,3 @@ get_config_express() {
   static DConfig config_express;
   return config_express;
 }
-
-#ifdef ANDROID
-static JavaVM *panda_jvm = NULL;
-
-/**
- * Called by Java when loading this library.
- */
-jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
-  panda_jvm = jvm;
-  return JNI_VERSION_1_4;
-}
-
-/**
- * Returns a pointer to the JavaVM object.
- */
-JavaVM *get_java_vm() {
-  nassertr(panda_jvm != NULL, NULL);
-  return panda_jvm;
-}
-
-/**
- * Returns a JNIEnv object for the current thread.  If it doesn't already
- * exist, attaches the JVM to this thread.
- */
-JNIEnv *get_jni_env() {
-  nassertr(panda_jvm != NULL, NULL);
-  JNIEnv *env = NULL;
-  int status = panda_jvm->GetEnv((void**) &env, JNI_VERSION_1_4);
-
-  if (status < 0 || env == NULL) {
-    express_cat.error() << "JVM is not available in this thread!\n";
-    return NULL;
-  }
-
-  return env;
-}
-#endif

+ 0 - 9
panda/src/express/config_express.h

@@ -28,10 +28,6 @@
 #include "executionEnvironment.h"
 #include "lineStream.h"
 
-#ifdef ANDROID
-#include <jni.h>
-#endif
-
 ConfigureDecl(config_express, EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS);
 NotifyCategoryDecl(express, EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS);
 NotifyCategoryDecl(clock, EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS);
@@ -65,9 +61,4 @@ END_PUBLISH
 
 extern EXPCL_PANDAEXPRESS void init_libexpress();
 
-#ifdef ANDROID
-extern EXPCL_PANDAEXPRESS JavaVM *get_java_vm();
-extern EXPCL_PANDAEXPRESS JNIEnv *get_jni_env();
-#endif
-
 #endif /* __CONFIG_UTIL_H__ */

+ 11 - 0
panda/src/pipeline/thread.I

@@ -300,6 +300,17 @@ prepare_for_exit() {
   ThreadImpl::prepare_for_exit();
 }
 
+#ifdef ANDROID
+/**
+ * Enables interaction with the Java VM on Android.  Returns null if the
+ * thread is not attached to the Java VM (or bind_thread was not called).
+ */
+INLINE JNIEnv *Thread::
+get_jni_env() const {
+  return _impl.get_jni_env();
+}
+#endif
+
 /**
  * Stores a PStats index to be associated with this thread.  This is used
  * internally by the PStatClient; you should not need to call this directly.

+ 8 - 0
panda/src/pipeline/thread.h

@@ -23,6 +23,10 @@
 #include "pnotify.h"
 #include "config_pipeline.h"
 
+#ifdef ANDROID
+typedef struct _JNIEnv JNIEnv;
+#endif
+
 class Mutex;
 class ReMutex;
 class MutexDebug;
@@ -128,6 +132,10 @@ public:
   INLINE void set_pstats_callback(PStatsCallback *pstats_callback);
   INLINE PStatsCallback *get_pstats_callback() const;
 
+#ifdef ANDROID
+  INLINE JNIEnv *get_jni_env() const;
+#endif
+
 private:
   static void init_main_thread();
   static void init_external_thread();

+ 16 - 0
panda/src/pipeline/threadPosixImpl.I

@@ -21,6 +21,9 @@ ThreadPosixImpl(Thread *parent_obj) :
   _joinable = false;
   _detached = false;
   _status = S_new;
+#ifdef ANDROID
+  _jni_env = nullptr;
+#endif
 }
 
 /**
@@ -60,6 +63,9 @@ bind_thread(Thread *thread) {
   }
   int result = pthread_setspecific(_pt_ptr_index, thread);
   nassertv(result == 0);
+#ifdef ANDROID
+  bind_java_thread();
+#endif
 }
 
 /**
@@ -112,3 +118,13 @@ yield() {
 INLINE void ThreadPosixImpl::
 consider_yield() {
 }
+
+#ifdef ANDROID
+/**
+ * Returns the JNIEnv object for the current thread.
+ */
+INLINE JNIEnv *ThreadPosixImpl::
+get_jni_env() const {
+  return _jni_env;
+}
+#endif

+ 67 - 10
panda/src/pipeline/threadPosixImpl.cxx

@@ -24,6 +24,8 @@
 #ifdef ANDROID
 #include "config_express.h"
 #include <jni.h>
+
+static JavaVM *java_vm = nullptr;
 #endif
 
 pthread_key_t ThreadPosixImpl::_pt_ptr_index = 0;
@@ -183,6 +185,53 @@ get_unique_id() const {
   return strm.str();
 }
 
+#ifdef ANDROID
+/**
+ * Attaches the thread to the Java virtual machine.  If this returns true, a
+ * JNIEnv pointer can be acquired using get_jni_env().
+ */
+bool ThreadPosixImpl::
+attach_java_vm() {
+  JNIEnv *env;
+  string thread_name = _parent_obj->get_name();
+  JavaVMAttachArgs args;
+  args.version = JNI_VERSION_1_2;
+  args.name = thread_name.c_str();
+  args.group = nullptr;
+  if (java_vm->AttachCurrentThread(&env, &args) != 0) {
+    thread_cat.error()
+      << "Failed to attach Java VM to thread "
+      << _parent_obj->get_name() << "!\n";
+    _jni_env = nullptr;
+    return false;
+  }
+  _jni_env = env;
+  return true;
+}
+
+/**
+ * Binds the Panda thread to the current thread, assuming that the current
+ * thread is already a valid attached Java thread.  Called by JNI_OnLoad.
+ */
+void ThreadPosixImpl::
+bind_java_thread() {
+  Thread *thread = Thread::get_current_thread();
+  nassertv(thread != nullptr);
+
+  // Get the JNIEnv for this Java thread, and store it on the corresponding
+  // Panda thread object.
+  JNIEnv *env;
+  if (java_vm->GetEnv((void **)&env, JNI_VERSION_1_4) == JNI_OK) {
+    nassertv(thread->_impl._jni_env == nullptr || thread->_impl._jni_env == env);
+    thread->_impl._jni_env = env;
+  } else {
+    thread_cat->error()
+      << "Called bind_java_thread() on thread "
+      << *thread << ", which is not attached to Java VM!\n";
+  }
+}
+#endif  // ANDROID
+
 /**
  * The entry point of each thread.
  */
@@ -209,14 +258,7 @@ root_func(void *data) {
 
 #ifdef ANDROID
     // Attach the Java VM to allow calling Java functions in this thread.
-    JavaVM *jvm = get_java_vm();
-    JNIEnv *env;
-    if (jvm == NULL || jvm->AttachCurrentThread(&env, NULL) != 0) {
-      thread_cat.error()
-        << "Failed to attach Java VM to thread "
-        << self->_parent_obj->get_name() << "!\n";
-      env = NULL;
-    }
+    self->attach_java_vm();
 #endif
 
     self->_parent_obj->thread_main();
@@ -238,8 +280,10 @@ root_func(void *data) {
     }
 
 #ifdef ANDROID
-    if (env != NULL) {
-      jvm->DetachCurrentThread();
+    // We cannot let the thread end without detaching it.
+    if (self->_jni_env != nullptr) {
+      java_vm->DetachCurrentThread();
+      self->_jni_env = nullptr;
     }
 #endif
 
@@ -276,4 +320,17 @@ init_pt_ptr_index() {
   nassertv(result == 0);
 }
 
+#ifdef ANDROID
+/**
+ * Called by Java when loading this library from the Java virtual machine.
+ */
+jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
+  // Store the JVM pointer globally.
+  java_vm = jvm;
+
+  ThreadPosixImpl::bind_java_thread();
+  return JNI_VERSION_1_4;
+}
+#endif  // ANDROID
+
 #endif  // THREAD_POSIX_IMPL

+ 14 - 0
panda/src/pipeline/threadPosixImpl.h

@@ -25,6 +25,10 @@
 
 #include <pthread.h>
 
+#ifdef ANDROID
+typedef struct _JNIEnv JNIEnv;
+#endif
+
 class Thread;
 
 /**
@@ -53,6 +57,12 @@ public:
   INLINE static void yield();
   INLINE static void consider_yield();
 
+#ifdef ANDROID
+  INLINE JNIEnv *get_jni_env() const;
+  bool attach_java_vm();
+  static void bind_java_thread();
+#endif
+
 private:
   static void *root_func(void *data);
   static void init_pt_ptr_index();
@@ -72,6 +82,10 @@ private:
   bool _detached;
   PStatus _status;
 
+#ifdef ANDROID
+  JNIEnv *_jni_env;
+#endif
+
   static pthread_key_t _pt_ptr_index;
   static bool _got_pt_ptr_index;
 };