android: properly support multiple Java threads

This commit is contained in:
rdb 2018-02-06 22:31:02 +01:00
parent 2dba9357bb
commit c1fccd311b
11 changed files with 163 additions and 65 deletions

View File

@ -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");

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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__ */

View File

@ -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.

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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;
};