Skip to content

Track GameActivity android_app pointer lifetime more carefully#236

Merged
rib merged 1 commit intomainfrom
rib/pr/game-activity-safe-destroy-handling
Mar 18, 2026
Merged

Track GameActivity android_app pointer lifetime more carefully#236
rib merged 1 commit intomainfrom
rib/pr/game-activity-safe-destroy-handling

Conversation

@rib
Copy link
Member

@rib rib commented Mar 18, 2026

Most of the same issues found in the native-activity backend when working on #234 (to safely drop ANativeActivity via onDestroy callback) also apply to the game-activity backend, which this PR addresses.

This ensures that the game-activity backend cleanly drops its android_app pointer once we're notified that the GameActivity is being destroyed and adds a mutex around the pointer that guarantees that it can't be freed while it's being dereferenced (because the same lock is required to respond to the onDestroy callback where the state gets freed).

This makes a number of backend details consistent with the native-activity backend:

  • The backend retains its own Looper reference instead of relying on the android_app reference.
  • The backend allocates its own JNI global reference for the Activity, instead of relying on the android_app reference.

Since this needed to add a hook to clear the android_app pointer after dispatching the callback for MainEvent::Destroy it also made sense to fix the MainEvent::TerminateWindow hook for clearing our NativeWindow so it also happens after the callback (as the API docs state).

Testing these changes with a minimal agdk-mainloop and agdk-egui example I see it's now possible to cleanly handle repeated activity start -> destroy -> start -> destroy cycles (e.g. due to config changes triggering a recreation of the activity). (When testing egui I did also have to patch Winit to ensure it exits the loop when receiving a Destroy event)

Fixes: #235
Fixes: #162
Addresses: #196

Most of the same issues found in the native-activity backend when
working on #234 (to safely drop ANativeActivity via onDestroy callback)
also apply to the game-activity backend, which this PR addresses.

This ensures that the game-activity backend cleanly drops its
`android_app` pointer once we're notified that the `GameActivity` is being
destroyed and adds a mutex around the pointer that guarantees that
it can't be freed while it's being dereferenced (because the same
lock is required to respond to the onDestroy callback where the
state gets freed).

This makes a number of backend details consistent with the
native-activity backend:
- The backend retains its own Looper reference instead of relying on
  the android_app reference.
- The backend allocates its own JNI global reference for the Activity,
  instead of relying on the android_app reference.

Since this needed to add a hook to clear the android_app pointer after
dispatching the callback for `MainEvent::Destroy` it also made sense to
fix the MainEvent::TerminateWindow hook for clearing our `NativeWindow`
so it also happens _after_ the callback (as the API docs state).

Testing these changes with a minimal agdk-mainloop and agdk-egui example
I see it's now possible to cleanly handle repeated activity start ->
destroy -> start -> destroy cycles (e.g. due to config changes
triggering a recreation of the activity). (When testing egui I did also
have to patch Winit to ensure it exits the loop when receiving a Destroy
event)

Fixes: #235
Fixes: #162
@rib rib force-pushed the rib/pr/game-activity-safe-destroy-handling branch from b5ff3f5 to d0779cc Compare March 18, 2026 01:14
@rib rib added bug Something isn't working safety Something that relates to a safety or soundness issue labels Mar 18, 2026
@rib rib merged commit 9163368 into main Mar 18, 2026
7 checks passed
@rib rib deleted the rib/pr/game-activity-safe-destroy-handling branch March 18, 2026 01:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working safety Something that relates to a safety or soundness issue

Projects

None yet

1 participant