Replace the direct access to JavaVM* and the global android Context instance with a function responsible for running attached correctly to the JVM. This saves having to replicate the logic for attaching an OS thread to the JVM. While here, check for any unhandled Java exceptions. This supersedes cl/11812. Change-Id: Ic9291fe64083d2bd983c4f8e309941b9c47d60c2 Reviewed-on: https://go-review.googlesource.com/14162 Reviewed-by: Nigel Tao <nigeltao@golang.org>
125 lines
3.1 KiB
Go
125 lines
3.1 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package mobileinit
|
|
|
|
/*
|
|
#include <jni.h>
|
|
#include <stdlib.h>
|
|
|
|
// current_vm is stored to initialize other cgo packages.
|
|
//
|
|
// As all the Go packages in a program form a single shared library,
|
|
// there can only be one JNI_OnLoad function for initialization. In
|
|
// OpenJDK there is JNI_GetCreatedJavaVMs, but this is not available
|
|
// on android.
|
|
JavaVM* current_vm;
|
|
|
|
// current_ctx is Android's android.context.Context. May be NULL.
|
|
jobject current_ctx;
|
|
|
|
char* lockJNI(uintptr_t* envp, int* attachedp) {
|
|
JNIEnv* env;
|
|
|
|
if (current_vm == NULL) {
|
|
return "no current JVM";
|
|
}
|
|
|
|
*attachedp = 0;
|
|
switch ((*current_vm)->GetEnv(current_vm, (void**)&env, JNI_VERSION_1_6)) {
|
|
case JNI_OK:
|
|
break;
|
|
case JNI_EDETACHED:
|
|
if ((*current_vm)->AttachCurrentThread(current_vm, &env, 0) != 0) {
|
|
return "cannot attach to JVM";
|
|
}
|
|
*attachedp = 1;
|
|
break;
|
|
case JNI_EVERSION:
|
|
return "bad JNI version";
|
|
default:
|
|
return "unknown JNI error from GetEnv";
|
|
}
|
|
|
|
*envp = (uintptr_t)env;
|
|
return NULL;
|
|
}
|
|
|
|
char* checkException(uintptr_t jnienv) {
|
|
jthrowable exc;
|
|
JNIEnv* env = (JNIEnv*)jnienv;
|
|
|
|
if (!(*env)->ExceptionCheck(env)) {
|
|
return NULL;
|
|
}
|
|
|
|
exc = (*env)->ExceptionOccurred(env);
|
|
(*env)->ExceptionClear(env);
|
|
|
|
jclass clazz = (*env)->FindClass(env, "java/lang/Throwable");
|
|
jmethodID toString = (*env)->GetMethodID(env, clazz, "toString", "()Ljava/lang/String;");
|
|
jobject msgStr = (*env)->CallObjectMethod(env, exc, toString);
|
|
return (char*)(*env)->GetStringUTFChars(env, msgStr, 0);
|
|
}
|
|
|
|
void unlockJNI() {
|
|
(*current_vm)->DetachCurrentThread(current_vm);
|
|
}
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"errors"
|
|
"runtime"
|
|
"unsafe"
|
|
)
|
|
|
|
// SetCurrentContext populates the global Context object with the specified
|
|
// current JavaVM instance (vm) and android.context.Context object (ctx).
|
|
// The android.context.Context object must be a global reference.
|
|
func SetCurrentContext(vm, ctx unsafe.Pointer) {
|
|
C.current_vm = (*C.JavaVM)(vm)
|
|
C.current_ctx = (C.jobject)(ctx)
|
|
}
|
|
|
|
// RunOnJVM runs fn on a new goroutine locked to an OS thread with a JNIEnv.
|
|
//
|
|
// RunOnJVM blocks until the call to fn is complete. Any Java
|
|
// exception or failure to attach to the JVM is returned as an error.
|
|
//
|
|
// The function fn takes vm, the current JavaVM*,
|
|
// env, the current JNIEnv*, and
|
|
// ctx, a jobject representing the global android.context.Context.
|
|
func RunOnJVM(fn func(vm, env, ctx uintptr) error) error {
|
|
errch := make(chan error)
|
|
go func() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
|
|
env := C.uintptr_t(0)
|
|
attached := C.int(0)
|
|
if errStr := C.lockJNI(&env, &attached); errStr != nil {
|
|
errch <- errors.New(C.GoString(errStr))
|
|
return
|
|
}
|
|
if attached != 0 {
|
|
defer C.unlockJNI()
|
|
}
|
|
|
|
vm := uintptr(unsafe.Pointer(C.current_vm))
|
|
if err := fn(vm, uintptr(env), uintptr(C.current_ctx)); err != nil {
|
|
errch <- err
|
|
return
|
|
}
|
|
|
|
if exc := C.checkException(env); exc != nil {
|
|
errch <- errors.New(C.GoString(exc))
|
|
C.free(unsafe.Pointer(exc))
|
|
return
|
|
}
|
|
errch <- nil
|
|
}()
|
|
return <-errch
|
|
}
|