app/internal/window: [Android] replace RegisterFragment with Do

Do is a function for accessing the underlying Android View in a safe
context, the main thread. Do is

- more general than RegisterFragment and may be expanded to other platforms
- simpler to implement (from the Gio side)

and as a bonus, the Do implementation avoids a race condition where
a call to RegisterFragment during an Activity re-create would be ignored.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2020-06-13 16:09:33 +02:00
parent f36674ddb3
commit 8688ed95c2
4 changed files with 60 additions and 57 deletions
+28 -14
View File
@@ -17,18 +17,32 @@ func AppContext() uintptr {
return window.AppContext()
}
// androidDriver is an interface that allows the Window's run method
// to call the RegisterFragment method of the Android window driver.
type androidDriver interface {
RegisterFragment(string)
}
// RegisterFragment constructs a Java instance of the specified class
// and registers it as a Fragment in the Context in which the View was
// created.
func (w *Window) RegisterFragment(del string) {
w.driverDo(func() {
d := w.driver.(androidDriver)
d.RegisterFragment(del)
})
// Do invokes the function with a JNI jobject handle to the underlying
// Android View. The function is invoked on the main thread, and the
// handle is invalidated after the function returns.
//
// Note: Do may deadlock if called from the same goroutine that receives from
// Events.
func (w *Window) Do(f func(view uintptr)) {
type androidDriver interface {
Do(f func(view uintptr)) bool
}
success := make(chan bool)
for {
driver := make(chan androidDriver, 1)
// two-stage process: first wait for a valid driver...
w.driverDo(func() {
driver <- w.driver.(androidDriver)
})
// .. then run the function on the main thread using the
// driver. The driver Do method returns false if the
// view was invalidated while switching to the main thread.
window.RunOnMain(func() {
d := <-driver
success <- d.Do(f)
})
if <-success {
break
}
}
}
-25
View File
@@ -191,31 +191,6 @@ public final class GioView extends SurfaceView implements Choreographer.FrameCal
return onBack(nhandle);
}
void registerFragment(String del) {
final Class cls;
try {
cls = getContext().getClassLoader().loadClass(del);
} catch (ClassNotFoundException e) {
throw new RuntimeException("RegisterFragment: fragment class not found: " + e.getMessage());
}
final Fragment frag;
try {
frag = (Fragment)cls.newInstance();
} catch (IllegalAccessException | InstantiationException | ExceptionInInitializerError | SecurityException | ClassCastException e) {
throw new RuntimeException("RegisterFragment: error instantiating fragment: " + e.getMessage());
}
final FragmentManager fm;
try {
fm = ((Activity)getContext()).getFragmentManager();
} catch (ClassCastException e) {
throw new RuntimeException("RegisterFragment: cannot get fragment manager from View Context: " + e.getMessage());
}
FragmentTransaction ft = fm.beginTransaction();
ft.add(frag, del);
ft.commitNow();
}
static private native long onCreateView(GioView view);
static private native void onDestroyView(long handle);
static private native void onStartView(long handle);
+23 -13
View File
@@ -80,7 +80,6 @@ type window struct {
mshowTextInput C.jmethodID
mhideTextInput C.jmethodID
mpostFrameCallback C.jmethodID
mRegisterFragment C.jmethodID
}
type jvalue uint64 // The largest JNI type fits in 64 bits.
@@ -193,7 +192,6 @@ func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.j
mshowTextInput: getMethodID(env, class, "showTextInput", "()V"),
mhideTextInput: getMethodID(env, class, "hideTextInput", "()V"),
mpostFrameCallback: getMethodID(env, class, "postFrameCallback", "()V"),
mRegisterFragment: getMethodID(env, class, "registerFragment", "(Ljava/lang/String;)V"),
}
wopts := <-mainWindow.out
w.callbacks = wopts.window
@@ -416,7 +414,6 @@ func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var env *C.JNIEnv
var detach bool
if res := C.gio_jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
if res != C.JNI_EDETACHED {
panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
@@ -424,14 +421,9 @@ func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
if C.gio_jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK {
panic(errors.New("runInJVM: AttachCurrentThread failed"))
}
detach = true
defer C.gio_jni_DetachCurrentThread(jvm)
}
if detach {
defer func() {
C.gio_jni_DetachCurrentThread(jvm)
}()
}
f(env)
}
@@ -537,11 +529,21 @@ func javaString(env *C.JNIEnv, str string) C.jstring {
return C.gio_jni_NewString(env, (*C.jchar)(unsafe.Pointer(&utf16Chars[0])), C.int(len(utf16Chars)))
}
func (w *window) RegisterFragment(del string) {
w.runOnMain(func(env *C.JNIEnv) {
jstr := javaString(env, del)
callVoidMethod(env, w.view, w.mRegisterFragment, jvalue(jstr))
// Do invokes the function with a global JNI handle to the view. If
// the view is destroyed, Do returns false and does not invoke the
// function.
//
// NOTE: Do must be invoked on the Android main thread.
func (w *window) Do(f func(view uintptr)) bool {
if w.view == 0 {
return false
}
runInJVM(javaVM(), func(env *C.JNIEnv) {
view := C.gio_jni_NewGlobalRef(env, w.view)
defer C.gio_jni_DeleteGlobalRef(env, view)
f(uintptr(view))
})
return true
}
func varArgs(args []jvalue) *C.jvalue {
@@ -645,6 +647,14 @@ func (w *window) ReadClipboard() {
})
}
// RunOnMain is the exported version of runOnMain without a JNI
// environement.
func RunOnMain(f func()) {
runOnMain(func(_ *C.JNIEnv) {
f()
})
}
// runOnMain runs a function on the Java main thread.
func runOnMain(f func(env *C.JNIEnv)) {
go func() {
+9 -5
View File
@@ -37,10 +37,14 @@ program's source code:
Android -- Dangerous Permissions
Certain permissions on Android are marked with a protection level of
"dangerous". This means that, in addition to including the relevant Gio
permission packages, your app will need to prompt the user specifically
to request access. This can be done with a java Fragment, installed using
(*app.Window).RegisterFragment(). For more information on dangerous
permissions, see: https://developer.android.com/guide/topics/permissions/overview#dangerous_permissions
"dangerous". This means that, in addition to including the relevant
Gio permission packages, your app will need to prompt the user
specifically to request access. To access the Android Activity
required for prompting, use the Do method on Window. Do exposes
the underlying Android View, on which the getContext method returns
the Activity.
For more information on dangerous permissions, see:
https://developer.android.com/guide/topics/permissions/overview#dangerous_permissions
*/
package permission