app: android activity lifecycle awareness
This CL fixes two bugs in the existing app implementation on android. The first, is it assumed a single NativeActivity instance is created per process. This is not true. If you open an app, hit the back button, then open it again, the original activity is destroyed and a new one is created. So only call main.main in the first onCreate. The second bug has to do with window lifetimes. Previously we only processed GL work while the window existed, as part of a paint cycle. This missed GL events called as part of a lifecycle downgrade when the window was destroyed. (I.e. the contents of onStop.) This CL fixes this by making the main android event processing loop last for the life of the process, not the window. Fixes golang/go#11804. Change-Id: Ia03e464aab5bc10ba75564b7ca11054515cda011 Reviewed-on: https://go-review.googlesource.com/12533 Reviewed-by: Nigel Tao <nigeltao@golang.org>
This commit is contained in:
Родитель
723de9f813
Коммит
5c91e60c93
|
@ -51,11 +51,18 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
int main_running = 0;
|
||||
|
||||
// Entry point from our subclassed NativeActivity.
|
||||
//
|
||||
// By here, the Go runtime has been initialized (as we are running in
|
||||
// -buildmode=c-shared) but main.main hasn't been called yet.
|
||||
// -buildmode=c-shared) but the first time it is called, Go's main.main
|
||||
// hasn't been called yet.
|
||||
//
|
||||
// The Activity may be created and destroyed multiple times throughout
|
||||
// the life of a single process. Each time, onCreate is called.
|
||||
void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_t savedStateSize) {
|
||||
if (!main_running) {
|
||||
JNIEnv* env = activity->env;
|
||||
|
||||
// Note that activity->clazz is mis-named.
|
||||
|
@ -79,9 +86,14 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_
|
|||
LOG_FATAL("missing main.main");
|
||||
}
|
||||
callMain(mainPC);
|
||||
main_running = 1;
|
||||
}
|
||||
|
||||
// These functions match the methods on Activity, described at
|
||||
// http://developer.android.com/reference/android/app/Activity.html
|
||||
//
|
||||
// Note that onNativeWindowResized is not called on resize. Avoid it.
|
||||
// https://code.google.com/p/android/issues/detail?id=180645
|
||||
activity->callbacks->onStart = onStart;
|
||||
activity->callbacks->onResume = onResume;
|
||||
activity->callbacks->onSaveInstanceState = onSaveInstanceState;
|
||||
|
@ -97,7 +109,5 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_
|
|||
activity->callbacks->onConfigurationChanged = onConfigurationChanged;
|
||||
activity->callbacks->onLowMemory = onLowMemory;
|
||||
|
||||
// Note that onNativeWindowResized is not called on resize. Avoid it.
|
||||
// https://code.google.com/p/android/issues/detail?id=180645
|
||||
onCreate(activity);
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@ import "C"
|
|||
import (
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
|
@ -79,12 +78,6 @@ func callMain(mainPC uintptr) {
|
|||
go callfn.CallFn(mainPC)
|
||||
}
|
||||
|
||||
//export onCreate
|
||||
func onCreate(activity *C.ANativeActivity) {
|
||||
config := windowConfigRead(activity)
|
||||
pixelsPerPt = config.pixelsPerPt
|
||||
}
|
||||
|
||||
//export onStart
|
||||
func onStart(activity *C.ANativeActivity) {
|
||||
}
|
||||
|
@ -106,6 +99,16 @@ func onPause(activity *C.ANativeActivity) {
|
|||
func onStop(activity *C.ANativeActivity) {
|
||||
}
|
||||
|
||||
//export onCreate
|
||||
func onCreate(activity *C.ANativeActivity) {
|
||||
// Set the initial configuration.
|
||||
//
|
||||
// Note we use unbuffered channels to talk to the activity loop, and
|
||||
// NativeActivity calls these callbacks sequentially, so configuration
|
||||
// will be set before <-windowRedrawNeeded is processed.
|
||||
windowConfigChange <- windowConfigRead(activity)
|
||||
}
|
||||
|
||||
//export onDestroy
|
||||
func onDestroy(activity *C.ANativeActivity) {
|
||||
}
|
||||
|
@ -132,19 +135,17 @@ func onNativeWindowRedrawNeeded(activity *C.ANativeActivity, window *C.ANativeWi
|
|||
|
||||
//export onNativeWindowDestroyed
|
||||
func onNativeWindowDestroyed(activity *C.ANativeActivity, window *C.ANativeWindow) {
|
||||
windowDestroyed <- true
|
||||
windowDestroyed <- window
|
||||
}
|
||||
|
||||
var queue *C.AInputQueue
|
||||
|
||||
//export onInputQueueCreated
|
||||
func onInputQueueCreated(activity *C.ANativeActivity, q *C.AInputQueue) {
|
||||
queue = q
|
||||
inputQueue <- q
|
||||
}
|
||||
|
||||
//export onInputQueueDestroyed
|
||||
func onInputQueueDestroyed(activity *C.ANativeActivity, q *C.AInputQueue) {
|
||||
queue = nil
|
||||
inputQueue <- nil
|
||||
}
|
||||
|
||||
//export onContentRectChanged
|
||||
|
@ -213,7 +214,8 @@ func onLowMemory(activity *C.ANativeActivity) {
|
|||
}
|
||||
|
||||
var (
|
||||
windowDestroyed = make(chan bool)
|
||||
inputQueue = make(chan *C.AInputQueue)
|
||||
windowDestroyed = make(chan *C.ANativeWindow)
|
||||
windowCreated = make(chan *C.ANativeWindow)
|
||||
windowRedrawNeeded = make(chan *C.ANativeWindow)
|
||||
windowRedrawDone = make(chan struct{})
|
||||
|
@ -223,21 +225,3 @@ var (
|
|||
func init() {
|
||||
registerGLViewportFilter()
|
||||
}
|
||||
|
||||
func main(f func(App)) {
|
||||
// Preserve this OS thread for the GL context created in windowDraw.
|
||||
runtime.LockOSThread()
|
||||
|
||||
donec := make(chan struct{})
|
||||
go func() {
|
||||
f(app{})
|
||||
close(donec)
|
||||
}()
|
||||
|
||||
for w := range windowCreated {
|
||||
if windowDraw(w, queue, donec) {
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
|
|
@ -80,11 +80,7 @@ func (app) EndPaint() {
|
|||
// This enforces that the final receive (for this paint cycle) on
|
||||
// gl.WorkAvailable happens before the send on endPaint.
|
||||
gl.Flush()
|
||||
|
||||
select {
|
||||
case endPaint <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
endPaint <- struct{}{}
|
||||
}
|
||||
|
||||
var filters []func(interface{}) interface{}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package app
|
||||
|
||||
/*
|
||||
#include <android/log.h>
|
||||
#include <android/native_activity.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/input.h>
|
||||
|
@ -24,57 +23,67 @@ const EGLint RGB_888[] = {
|
|||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLDisplay display;
|
||||
EGLSurface surface;
|
||||
EGLDisplay display = NULL;
|
||||
EGLSurface surface = NULL;
|
||||
|
||||
#define LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, "Go", __VA_ARGS__)
|
||||
char* initEGLDisplay() {
|
||||
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (!eglInitialize(display, 0, 0)) {
|
||||
return "EGL initialize failed";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void createEGLWindow(ANativeWindow* window) {
|
||||
char* createEGLSurface(ANativeWindow* window) {
|
||||
char* err;
|
||||
EGLint numConfigs, format;
|
||||
EGLConfig config;
|
||||
EGLContext context;
|
||||
|
||||
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (!eglInitialize(display, 0, 0)) {
|
||||
LOG_ERROR("EGL initialize failed");
|
||||
return;
|
||||
if (display == 0) {
|
||||
if ((err = initEGLDisplay()) != NULL) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!eglChooseConfig(display, RGB_888, &config, 1, &numConfigs)) {
|
||||
LOG_ERROR("EGL choose RGB_888 config failed");
|
||||
return;
|
||||
return "EGL choose RGB_888 config failed";
|
||||
}
|
||||
if (numConfigs <= 0) {
|
||||
LOG_ERROR("EGL no config found");
|
||||
return;
|
||||
return "EGL no config found";
|
||||
}
|
||||
|
||||
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
|
||||
if (ANativeWindow_setBuffersGeometry(window, 0, 0, format) != 0) {
|
||||
LOG_ERROR("EGL set buffers geometry failed");
|
||||
return;
|
||||
return "EGL set buffers geometry failed";
|
||||
}
|
||||
|
||||
surface = eglCreateWindowSurface(display, config, window, NULL);
|
||||
if (surface == EGL_NO_SURFACE) {
|
||||
LOG_ERROR("EGL create surface failed");
|
||||
return;
|
||||
return "EGL create surface failed";
|
||||
}
|
||||
|
||||
const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
|
||||
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
|
||||
|
||||
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {
|
||||
LOG_ERROR("eglMakeCurrent failed");
|
||||
return;
|
||||
return "eglMakeCurrent failed";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#undef LOG_ERROR
|
||||
char* destroyEGLSurface() {
|
||||
if (!eglDestroySurface(display, surface)) {
|
||||
return "EGL destroy surface failed";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/mobile/event/config"
|
||||
"golang.org/x/mobile/event/lifecycle"
|
||||
|
@ -84,7 +93,18 @@ import (
|
|||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan struct{}) (done bool) {
|
||||
func main(f func(App)) {
|
||||
// Preserve this OS thread for the GL context created below.
|
||||
runtime.LockOSThread()
|
||||
|
||||
donec := make(chan struct{})
|
||||
go func() {
|
||||
f(app{})
|
||||
close(donec)
|
||||
}()
|
||||
|
||||
var q *C.AInputQueue
|
||||
|
||||
// Android can send a windowRedrawNeeded event any time, including
|
||||
// in the middle of a paint cycle. The redraw event may have changed
|
||||
// the size of the screen, so any partial painting is now invalidated.
|
||||
|
@ -97,15 +117,27 @@ func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan struct{}) (
|
|||
// windowRedrawDone is signalled, allowing onNativeWindowRedrawNeeded
|
||||
// to return.
|
||||
var redrawGen, paintGen uint32
|
||||
|
||||
for {
|
||||
processEvents(queue)
|
||||
if q != nil {
|
||||
processEvents(q)
|
||||
}
|
||||
select {
|
||||
case <-windowCreated:
|
||||
case q = <-inputQueue:
|
||||
case <-donec:
|
||||
return true
|
||||
return
|
||||
case cfg := <-windowConfigChange:
|
||||
// TODO save orientation
|
||||
pixelsPerPt = cfg.pixelsPerPt
|
||||
case w := <-windowRedrawNeeded:
|
||||
newWindow := C.surface == nil
|
||||
if newWindow {
|
||||
if errStr := C.createEGLSurface(w); errStr != nil {
|
||||
log.Printf("app: %s (%s)", C.GoString(errStr), eglGetError())
|
||||
return
|
||||
}
|
||||
}
|
||||
sendLifecycle(lifecycle.StageFocused)
|
||||
widthPx := int(C.ANativeWindow_getWidth(w))
|
||||
heightPx := int(C.ANativeWindow_getHeight(w))
|
||||
|
@ -116,29 +148,44 @@ func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan struct{}) (
|
|||
HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt),
|
||||
PixelsPerPt: pixelsPerPt,
|
||||
}
|
||||
if paintGen == 0 {
|
||||
paintGen++
|
||||
C.createEGLWindow(w)
|
||||
redrawGen++
|
||||
if newWindow {
|
||||
// New window, begin paint loop.
|
||||
// TODO(crawshaw): If we get a <-windowCreated in between sending a
|
||||
// paint.Event and receiving on endPaint, we can double-paint. (BUG)
|
||||
paintGen = redrawGen
|
||||
eventsIn <- paint.Event{}
|
||||
}
|
||||
redrawGen++
|
||||
case <-windowDestroyed:
|
||||
if C.surface != nil {
|
||||
if errStr := C.destroyEGLSurface(); errStr != nil {
|
||||
log.Printf("app: %s (%s)", C.GoString(errStr), eglGetError())
|
||||
return
|
||||
}
|
||||
}
|
||||
C.surface = nil
|
||||
sendLifecycle(lifecycle.StageAlive)
|
||||
return false
|
||||
case <-gl.WorkAvailable:
|
||||
gl.DoWork()
|
||||
case <-endPaint:
|
||||
if paintGen == redrawGen {
|
||||
if C.surface != nil {
|
||||
// eglSwapBuffers blocks until vsync.
|
||||
C.eglSwapBuffers(C.display, C.surface)
|
||||
if C.eglSwapBuffers(C.display, C.surface) == C.EGL_FALSE {
|
||||
log.Printf("app: failed to swap buffers (%s)", eglGetError())
|
||||
}
|
||||
}
|
||||
select {
|
||||
case windowRedrawDone <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
paintGen = redrawGen
|
||||
if C.surface != nil {
|
||||
redrawGen++
|
||||
eventsIn <- paint.Event{}
|
||||
}
|
||||
paintGen = redrawGen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,3 +233,40 @@ func processEvent(e *C.AInputEvent) {
|
|||
log.Printf("unknown input event, type=%d", C.AInputEvent_getType(e))
|
||||
}
|
||||
}
|
||||
|
||||
func eglGetError() string {
|
||||
switch errNum := C.eglGetError(); errNum {
|
||||
case C.EGL_SUCCESS:
|
||||
return "EGL_SUCCESS"
|
||||
case C.EGL_NOT_INITIALIZED:
|
||||
return "EGL_NOT_INITIALIZED"
|
||||
case C.EGL_BAD_ACCESS:
|
||||
return "EGL_BAD_ACCESS"
|
||||
case C.EGL_BAD_ALLOC:
|
||||
return "EGL_BAD_ALLOC"
|
||||
case C.EGL_BAD_ATTRIBUTE:
|
||||
return "EGL_BAD_ATTRIBUTE"
|
||||
case C.EGL_BAD_CONTEXT:
|
||||
return "EGL_BAD_CONTEXT"
|
||||
case C.EGL_BAD_CONFIG:
|
||||
return "EGL_BAD_CONFIG"
|
||||
case C.EGL_BAD_CURRENT_SURFACE:
|
||||
return "EGL_BAD_CURRENT_SURFACE"
|
||||
case C.EGL_BAD_DISPLAY:
|
||||
return "EGL_BAD_DISPLAY"
|
||||
case C.EGL_BAD_SURFACE:
|
||||
return "EGL_BAD_SURFACE"
|
||||
case C.EGL_BAD_MATCH:
|
||||
return "EGL_BAD_MATCH"
|
||||
case C.EGL_BAD_PARAMETER:
|
||||
return "EGL_BAD_PARAMETER"
|
||||
case C.EGL_BAD_NATIVE_PIXMAP:
|
||||
return "EGL_BAD_NATIVE_PIXMAP"
|
||||
case C.EGL_BAD_NATIVE_WINDOW:
|
||||
return "EGL_BAD_NATIVE_WINDOW"
|
||||
case C.EGL_CONTEXT_LOST:
|
||||
return "EGL_CONTEXT_LOST"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown EGL err: %d", errNum)
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче