diff --git a/app/Go.java b/app/Go.java index e0c1830..ece8c4c 100644 --- a/app/Go.java +++ b/app/Go.java @@ -34,5 +34,4 @@ public final class Go { private static boolean running = false; private static native void run(Context ctx); - private static native void waitForRun(); } diff --git a/app/android.c b/app/android.c index 9683e6c..9b9b019 100644 --- a/app/android.c +++ b/app/android.c @@ -19,19 +19,6 @@ #define LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, "Go", __VA_ARGS__) #define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "Go", __VA_ARGS__) -jint JNI_OnLoad(JavaVM* vm, void* reserved) { - current_vm = vm; - current_ctx = NULL; - current_native_activity = NULL; - - JNIEnv* env; - if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { - return -1; - } - - return JNI_VERSION_1_6; -} - static jclass find_class(JNIEnv *env, const char *class_name) { jclass clazz = (*env)->FindClass(env, class_name); if (clazz == NULL) { @@ -50,6 +37,44 @@ static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const return m; } +static jclass app_class_loader; +static jmethodID app_find_class_fn; + +// app_find_class finds the given class using the class loader cached +// during JNI_OnLoad. This allows the bind/java package and other +// part of JNI to dynamically load java classes from non-Java threads. +// +// http://developer.android.com/training/articles/perf-jni.html#faq_FindClass +jclass app_find_class(JNIEnv* env, const char* name) { + jstring classname = (*env)->NewStringUTF(env, name); + return (*env)->CallObjectMethod(env, app_class_loader, app_find_class_fn, classname); +} + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { + return -1; + } + + current_vm = vm; + current_ctx = NULL; + current_native_activity = NULL; + + // cache the class loader for use in JNI threads. + jclass a_clazz = (*env)->FindClass(env, "go/Go"); + jclass clazz_clazz = (*env)->GetObjectClass(env, a_clazz); + jclass class_loader_clazz = (*env)->FindClass(env, "java/lang/ClassLoader"); + jmethodID get_class_loader = (*env)->GetMethodID(env, clazz_clazz, "getClassLoader", + "()Ljava/lang/ClassLoader;"); + jclass loader = (*env)->CallObjectMethod(env, a_clazz, get_class_loader); + app_class_loader = (*env)->NewGlobalRef(env, loader); + + app_find_class_fn = (*env)->GetMethodID(env, class_loader_clazz, "findClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + + return JNI_VERSION_1_6; +} + static void init_from_context() { if (current_ctx == NULL) { return; @@ -163,6 +188,7 @@ Java_go_Go_run(JNIEnv* env, jclass clazz, jobject ctx) { jclass context_clazz = find_class(env, "android/content/Context"); jmethodID getassets = find_method( env, context_clazz, "getAssets", "()Landroid/content/res/AssetManager;"); + // Prevent the java AssetManager from being GC'd jobject asset_manager_ref = (*env)->NewGlobalRef( env, (*env)->CallObjectMethod(env, current_ctx, getassets)); diff --git a/app/android.go b/app/android.go index fc8a156..02208c1 100644 --- a/app/android.go +++ b/app/android.go @@ -46,6 +46,8 @@ JavaVM* current_vm; // current_ctx is Android's android.context.Context. May be NULL. jobject current_ctx; +jclass app_find_class(JNIEnv* env, const char* name); + // current_native_activity is the Android ANativeActivity. May be NULL. ANativeActivity* current_native_activity; @@ -192,6 +194,12 @@ func (androidState) JavaVM() unsafe.Pointer { return unsafe.Pointer(C.current_vm) } +// ClassFinder returns a C function pointer for finding a given class using +// the app class loader. (jclass) (*fn)(JNIEnv*, const char*). +func (androidState) ClassFinder() unsafe.Pointer { + return unsafe.Pointer(C.app_find_class) +} + func (androidState) AndroidContext() unsafe.Pointer { return unsafe.Pointer(C.current_ctx) } diff --git a/bind/java/seq_android.c b/bind/java/seq_android.c index 3bb45aa..b5bd470 100644 --- a/bind/java/seq_android.c +++ b/bind/java/seq_android.c @@ -159,15 +159,28 @@ static void unpin_arrays(JNIEnv *env, mem *m) { m->pinned = NULL; } +static void describe_exception(JNIEnv* env) { + jthrowable exc = (*env)->ExceptionOccurred(env); + if (exc) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } +} + +// find_class_fn finds a class with the given name using the app class loader. +// It is implemented in the app package and is initialized during the init_seq call. +static jclass (*find_class_fn)(JNIEnv*, const char*); static jfieldID find_field(JNIEnv *env, const char *class_name, const char *field_name, const char *field_type) { - jclass clazz = (*env)->FindClass(env, class_name); + jclass clazz = find_class_fn(env, class_name); if (clazz == NULL) { + describe_exception(env); LOG_FATAL("cannot find %s", class_name); return NULL; } jfieldID id = (*env)->GetFieldID(env, clazz, field_name , field_type); if(id == NULL) { + describe_exception(env); LOG_FATAL("no %s/%s field", field_name, field_type); return NULL; } @@ -175,26 +188,28 @@ static jfieldID find_field(JNIEnv *env, const char *class_name, const char *fiel } static jclass find_class(JNIEnv *env, const char *class_name) { - jclass clazz = (*env)->FindClass(env, class_name); + jclass clazz = find_class_fn(env, class_name); if (clazz == NULL) { + describe_exception(env); LOG_FATAL("cannot find %s", class_name); return NULL; } return (*env)->NewGlobalRef(env, clazz); } -void init_seq(void *javavm) { +void init_seq(void *javavm, void *classfinder) { JavaVM *vm = (JavaVM*)javavm; + find_class_fn = (jclass (*)(JNIEnv*, const char*))classfinder; + JNIEnv *env; int res = (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6); if (res == JNI_EDETACHED) { - if ((*vm)->AttachCurrentThread(vm, &env, NULL) != 0) { + if ((*vm)->AttachCurrentThread(vm, &env, NULL) != JNI_OK) { LOG_FATAL("cannot attach to current_vm"); } - } else if (res != 0) { + } else if (res != JNI_OK) { LOG_FATAL("bad vm env: %d", res); } - memptr_id = find_field(env, "go/Seq", "memptr", "J"); receive_refnum_id = find_field(env, "go/Seq$Receive", "refnum", "I"); receive_handle_id = find_field(env, "go/Seq$Receive", "handle", "I"); diff --git a/bind/java/seq_android.go b/bind/java/seq_android.go index d5a25b5..e7b94df 100644 --- a/bind/java/seq_android.go +++ b/bind/java/seq_android.go @@ -82,7 +82,11 @@ func initSeq() { vm := app.State.(interface { JavaVM() unsafe.Pointer }).JavaVM() - C.init_seq(vm) + classFinder := app.State.(interface { + ClassFinder() unsafe.Pointer + }).ClassFinder() + + C.init_seq(vm, classFinder) } func seqToBuf(bufptr **C.uint8_t, lenptr *C.size_t, buf *seq.Buffer) { diff --git a/bind/java/seq_android.h b/bind/java/seq_android.h index e56f50b..5ae09c5 100644 --- a/bind/java/seq_android.h +++ b/bind/java/seq_android.h @@ -2,4 +2,4 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -void init_seq(void* vm); +void init_seq(void* vm, void* classfinder);