diff --git a/app/Go.java b/app/Go.java index 975834e..df3dd4d 100644 --- a/app/Go.java +++ b/app/Go.java @@ -13,7 +13,7 @@ import android.util.Log; // Go code. public final class Go { // init loads libgojni.so and starts the runtime. - public static void init(Context context) { + public static void init(final Context ctx) { if (Looper.myLooper() != Looper.getMainLooper()) { Log.wtf("Go", "Go.init must be called from main thread (looper="+Looper.myLooper().toString()+")"); } @@ -29,7 +29,7 @@ public final class Go { new Thread("GoMain") { public void run() { - Go.run(); + Go.run(ctx); } }.start(); @@ -42,6 +42,6 @@ public final class Go { private static boolean running = false; - private static native void run(); + private static native void run(Context ctx); private static native void waitForRun(); } diff --git a/app/android.c b/app/android.c index 5e98b75..5d94f12 100644 --- a/app/android.c +++ b/app/android.c @@ -15,16 +15,13 @@ #include #include "_cgo_export.h" -// Defined in android.go. -extern pthread_cond_t go_started_cond; -extern pthread_mutex_t go_started_mu; -extern int go_started; -extern JavaVM* current_vm; - +// Defined in the Go runtime. static int (*_rt0_arm_linux1)(int argc, char** argv); 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) { @@ -92,7 +89,10 @@ void InitGoRuntime() { // Runtime entry point when using NativeActivity. void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_t savedStateSize) { + // Note that activity->clazz is mis-named. current_vm = activity->vm; + current_ctx = (*activity->env)->NewGlobalRef(activity->env, activity->clazz); + current_native_activity = activity; InitGoRuntime(); @@ -121,7 +121,8 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_ // Runtime entry point when embedding Go in a Java App. JNIEXPORT void JNICALL -Java_go_Go_run(JNIEnv* env, jclass clazz) { +Java_go_Go_run(JNIEnv* env, jclass clazz, jobject ctx) { + current_ctx = (*env)->NewGlobalRef(env, ctx); init_go_runtime(NULL); } diff --git a/app/android.go b/app/android.go index 59396fc..4b85413 100644 --- a/app/android.go +++ b/app/android.go @@ -34,6 +34,12 @@ int go_started; // on android. JavaVM* current_vm; +// current_ctx is Android's android.context.Context. May be NULL. +jobject current_ctx; + +// current_native_activity is the Android ANativeActivity. May be NULL. +ANativeActivity* current_native_activity; + // build_auxv builds an ELF auxiliary vector for initializing the Go // runtime. While there does not appear to be any spec for this // format, there are some notes in @@ -166,10 +172,16 @@ func onConfigurationChanged(activity *C.ANativeActivity) { func onLowMemory(activity *C.ANativeActivity) { } -// JavaInit is an initialization function registered by the package -// golang.org/x/mobile/bind/java. It gives the Java language -// bindings access to the JNI *JavaVM object. -var JavaInit func(javaVM uintptr) +type androidState struct { +} + +func (androidState) JavaVM() unsafe.Pointer { + return unsafe.Pointer(C.current_vm) +} + +func (androidState) AndroidContext() unsafe.Pointer { + return unsafe.Pointer(C.current_ctx) +} var ( windowDestroyed = make(chan bool) @@ -227,6 +239,23 @@ func (a *asset) Close() error { return nil } +func runStart(cb Callbacks) { + State = androidState{} + + if cb.Start != nil { + cb.Start() + } +} + +// notifyInitDone informs Java that the program is initialized. +// A NativeActivity will not create a window until this is called. +func notifyInitDone() { + C.pthread_mutex_lock(&C.go_started_mu) + C.go_started = 1 + C.pthread_cond_signal(&C.go_started_cond) + C.pthread_mutex_unlock(&C.go_started_mu) +} + func run(cb Callbacks) { // We want to keep the event loop on a consistent OS thread. runtime.LockOSThread() @@ -237,20 +266,12 @@ func run(cb Callbacks) { C.free(unsafe.Pointer(ctag)) C.free(unsafe.Pointer(cstr)) - if JavaInit != nil { - JavaInit(uintptr(unsafe.Pointer(C.current_vm))) - } - - // Inform Java that the program is initialized. - C.pthread_mutex_lock(&C.go_started_mu) - C.go_started = 1 - C.pthread_cond_signal(&C.go_started_cond) - C.pthread_mutex_unlock(&C.go_started_mu) - - for { - select { - case w := <-windowCreated: - windowDrawLoop(cb, w, queue) - } + if C.current_native_activity == nil { + runStart(cb) + notifyInitDone() + select {} + } else { + notifyInitDone() + windowDrawLoop(cb, <-windowCreated, queue) } } diff --git a/app/app.go b/app/app.go index df5368f..75652f7 100644 --- a/app/app.go +++ b/app/app.go @@ -25,8 +25,12 @@ type Callbacks struct { // Start is called when the app enters the foreground. // The app will start receiving Draw and Touch calls. // - // Window geometry will be configured and an OpenGL context - // will be available. + // If the app is responsible for the screen (that is, it is an + // all-Go app), then Window geometry will be configured and an + // OpenGL context will be available during Start. + // + // If this is a library, Start will be called before the + // app is told that Go has finished initialization. // // Start is an equivalent lifecycle state to onStart() on // Android and applicationDidBecomeActive on iOS. @@ -82,3 +86,17 @@ type ReadSeekCloser interface { io.ReadSeeker io.Closer } + +// State is global application-specific state. +// +// The State variable also holds operating system specific state. +// Android apps have the extra methods: +// +// // JavaVM returns a JNI *JavaVM. +// JavaVM() unsafe.Pointer +// +// // AndroidContext returns a jobject for the app android.context.Context. +// AndroidContext() unsafe.Pointer +// +// State is not valid until Run has been called. +var State interface{} diff --git a/app/loop_android.go b/app/loop_android.go index 9a7504f..7605d9e 100644 --- a/app/loop_android.go +++ b/app/loop_android.go @@ -101,6 +101,9 @@ func windowDrawLoop(cb Callbacks, w *C.ANativeWindow, queue *C.AInputQueue) { geom.Width = geom.Pt(float32(C.windowWidth) / geom.PixelsPerPt) geom.Height = geom.Pt(float32(C.windowHeight) / geom.PixelsPerPt) + // Wait until geometry and GL is initialized before cb.Start. + runStart(cb) + // We start here rather than onStart so the window exists and the Gl // context is configured. if cb.Start != nil { diff --git a/bind/java/javatest.go b/bind/java/javatest.go index 0787f2d..08b0e04 100644 --- a/bind/java/javatest.go +++ b/bind/java/javatest.go @@ -8,11 +8,11 @@ package main import ( "golang.org/x/mobile/app" + "golang.org/x/mobile/bind/java" - _ "golang.org/x/mobile/bind/java" _ "golang.org/x/mobile/bind/java/testpkg/go_testpkg" ) func main() { - app.Run(app.Callbacks{}) + app.Run(app.Callbacks{Start: java.Init}) } diff --git a/bind/java/seq_android.go b/bind/java/seq_android.go index 64fdec4..4af5e13 100644 --- a/bind/java/seq_android.go +++ b/bind/java/seq_android.go @@ -78,10 +78,13 @@ func init() { res.out = make(map[int32]*seq.Buffer) } -func init() { - app.JavaInit = func(javaVM uintptr) { - C.init_seq(unsafe.Pointer(javaVM)) - } +// Init initializes communication with Java. +// Typically called from the Start callback in app.Run. +func Init() { + vm := app.State.(interface { + JavaVM() unsafe.Pointer + }).JavaVM() + C.init_seq(vm) } func seqToBuf(bufptr **C.uint8_t, lenptr *C.size_t, buf *seq.Buffer) { diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go index b2551b6..379b3f6 100644 --- a/cmd/gomobile/bind.go +++ b/cmd/gomobile/bind.go @@ -295,13 +295,13 @@ var androidMainTmpl = template.Must(template.New("android.go").Parse(` package main import ( - "golang.org/x/mobile/app" + "golang.org/x/mobile/app" + "golang.org/x/mobile/bind/java" - _ "golang.org/x/mobile/bind/java" - _ "{{.}}" + _ "{{.}}" ) func main() { - app.Run(app.Callbacks{}) + app.Run(app.Callbacks{Start: java.Init}) } `))