bind/java: use the class loader cached during JNI_OnLoad call.

This allows the application class loader to be used when
the bind/java package or other part of JNI dynamically
loads java classes from a non-Java thread.

http://developer.android.com/training/articles/perf-jni.html#faq_FindClass

Fixes golang/go#10668.

Change-Id: I44df3a9362617fa6dd26ddf88247e4fdaee7c7e8
Reviewed-on: https://go-review.googlesource.com/9732
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Hyang-Ah (Hana) Kim 2015-05-05 15:59:45 -04:00 committed by Hyang-Ah Hana Kim
parent 696139153c
commit 601608a0e0
6 changed files with 74 additions and 22 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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