diff --git a/build/android/app/src/main/java/com/wails/app/MainActivity.java b/build/android/app/src/main/java/com/wails/app/MainActivity.java index 0840ded..9c84164 100644 --- a/build/android/app/src/main/java/com/wails/app/MainActivity.java +++ b/build/android/app/src/main/java/com/wails/app/MainActivity.java @@ -891,13 +891,41 @@ public class MainActivity extends AppCompatActivity { } } + /** + * The activity going away is not the app shutting down. + * + *

The scaffold called {@code bridge.shutdown()} here, which is + * the natural reading of onDestroy and is wrong for this app twice + * over. Android destroys and recreates an activity for a + * configuration change the manifest does not declare, under memory + * pressure, and on every background if the user has "Don't keep + * activities" on -- all **without restarting the process**. And + * when the user really does leave, this app's reason for existing + * in the background is that a song is playing, which is what the + * {@code mediaPlayback} foreground service is holding the process + * alive for. Either way, tearing the Go side down here would stop + * the music. + * + *

It was harmless only by accident: {@code nativeShutdown} calls + * {@code App.Quit()}, whose Android {@code destroy()} is an empty + * method, and {@code Run()}'s deferred {@code shutdownServices()} + * can never fire because Android's {@code platformRun} is + * {@code select{}} and does not return. So no {@code + * ServiceShutdown} has ever run on Android, and removing this call + * changes nothing today -- it stops the day someone implements + * {@code destroy()} from silently killing playback on a rotation. + * + *

There is no callback for "the process is going away"; Android + * simply kills it. Durability on this platform is the persist + * writers, which submit on every mutation rather than at exit. + * + *

See #52, and CLAUDE.md, "An activity is a view onto the + * process". + */ @Override protected void onDestroy() { super.onDestroy(); unregisterSystemEventReceivers(); - if (bridge != null) { - bridge.shutdown(); - } if (webView != null) { webView.destroy(); } diff --git a/build/android/app/src/main/java/com/wails/app/WailsBridge.java b/build/android/app/src/main/java/com/wails/app/WailsBridge.java index 6198770..ce912cc 100644 --- a/build/android/app/src/main/java/com/wails/app/WailsBridge.java +++ b/build/android/app/src/main/java/com/wails/app/WailsBridge.java @@ -129,7 +129,24 @@ public class WailsBridge { } /** - * Initialize the native Go library + * Initialize the native Go library. + * + *

{@code initialized} is deliberately per-instance, and making + * it {@code static} is the trap this comment exists for. A + * recreated activity builds a new bridge and calls this again, in a + * process where the native library is already loaded and Go's + * {@code main()} is already running -- so "initialise once per + * process" looks like exactly the right rule. It is not, because + * {@code nativeInit} does two things: it runs + * {@code go mainFunc()}, and it stores the global JNI reference to + * this bridge. Skip it and Go keeps executing JavaScript + * against the destroyed activity's WebView: the app opens, renders, + * and never receives another backend event. + * + *

So this is called every time, and the half that must not repeat + * is latched on the Go side instead, at the top of {@code main()} -- + * which is also where the damage was ({@code os.Exit(1)}), and the + * only place that can see it. See #52. */ public void initialize() { if (initialized) {