diff --git a/panda/src/android/PandaActivity.java b/panda/src/android/PandaActivity.java index feba4baa4e..8f3240900e 100644 --- a/panda/src/android/PandaActivity.java +++ b/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"); diff --git a/panda/src/android/android_main.cxx b/panda/src/android/android_main.cxx index 32c692489f..8a3d6135fd 100644 --- a/panda/src/android/android_main.cxx +++ b/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; diff --git a/panda/src/android/config_android.cxx b/panda/src/android/config_android.cxx index bf9056c562..92d986669d 100644 --- a/panda/src/android/config_android.cxx +++ b/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); diff --git a/panda/src/android/pnmFileTypeAndroidReader.cxx b/panda/src/android/pnmFileTypeAndroidReader.cxx index 50c69cb59c..ac95ce98fd 100644 --- a/panda/src/android/pnmFileTypeAndroidReader.cxx +++ b/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); diff --git a/panda/src/express/config_express.cxx b/panda/src/express/config_express.cxx index dd590fcb38..042b6d8db1 100644 --- a/panda/src/express/config_express.cxx +++ b/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 diff --git a/panda/src/express/config_express.h b/panda/src/express/config_express.h index 53f33ca36a..eabe20f834 100644 --- a/panda/src/express/config_express.h +++ b/panda/src/express/config_express.h @@ -28,10 +28,6 @@ #include "executionEnvironment.h" #include "lineStream.h" -#ifdef ANDROID -#include -#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__ */ diff --git a/panda/src/pipeline/thread.I b/panda/src/pipeline/thread.I index 4fcd8a447e..10d7b6e44f 100644 --- a/panda/src/pipeline/thread.I +++ b/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. diff --git a/panda/src/pipeline/thread.h b/panda/src/pipeline/thread.h index b519aa75a6..730deb5872 100644 --- a/panda/src/pipeline/thread.h +++ b/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(); diff --git a/panda/src/pipeline/threadPosixImpl.I b/panda/src/pipeline/threadPosixImpl.I index 93bdce8e8c..62b6026061 100644 --- a/panda/src/pipeline/threadPosixImpl.I +++ b/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 diff --git a/panda/src/pipeline/threadPosixImpl.cxx b/panda/src/pipeline/threadPosixImpl.cxx index 54b3dfd630..5104c67926 100644 --- a/panda/src/pipeline/threadPosixImpl.cxx +++ b/panda/src/pipeline/threadPosixImpl.cxx @@ -24,6 +24,8 @@ #ifdef ANDROID #include "config_express.h" #include + +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 diff --git a/panda/src/pipeline/threadPosixImpl.h b/panda/src/pipeline/threadPosixImpl.h index 0168c38665..ee279c98dd 100644 --- a/panda/src/pipeline/threadPosixImpl.h +++ b/panda/src/pipeline/threadPosixImpl.h @@ -25,6 +25,10 @@ #include +#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; };