Testing process death, not just rotation.
Activity recreation tests miss what happens when Android kills your process in the background. Here is a real check that runs in CI on API 26 and 36.
Ask how an app survives process death and you will often hear "we test rotation". Rotation recreates the Activity, but the process, the ViewModels and everything in memory stay alive. When Android reclaims a backgrounded app, all of that is gone, and only what you saved in the instance state comes back.
Those are different failures, so they need different tests.
What should survive
In Offer Catalog the search query and the selected category live in SavedStateHandle. That is a deliberate scope: after process death the user should see the same search, and the first page reloads. The loaded pages themselves are not persisted, and the README says so. A test should check that promise, nothing more and nothing less.
Killing the process for real
The check runs on the host, outside the app, through adb. Roughly:
- Launch the app, type
coastin search, pick the Travel category and wait for the filtered result. - Remember the process ID and press Home.
- Read
dumpsys activity activitiesuntil the Activity is reported as stopped and as having saved state. Killing it earlier would test a different situation. - Send
SIGKILLto the app process. On a debug buildrun-asmakes that possible without root:
old_pid = shell("pidof", PACKAGE)
shell("input", "keyevent", "3") # Home
wait_for(is_background, "stopped activity with saved instance state")
shell("run-as", PACKAGE, "kill", "-9", old_pid)
wait_for(
lambda: not any(line.split()[-1:] == [PACKAGE] for line in shell("ps", "-A").splitlines()),
"background process termination",
)- Start the app again from the launcher intent, which returns to the retained task.
- Assert that the query is still
coast, the Travel chip is selected, the filtered offer is visible, and the process ID is new.
That last assertion is the one people skip. Without it, a test can pass because the process was never actually killed.
Running it in CI
The same script runs in GitHub Actions on emulators with API 26 and API 36, separately from the instrumented Activity recreation tests. Device polling uses bounded waits with timeouts, while the coroutine unit tests keep using virtual time, so the slow, real-device part stays small and isolated.
Limits worth knowing
run-asonly works for debuggable builds, so this is a debug-build check.- It verifies one journey, not every screen. For an app with many entry points I would add a few more journeys rather than one giant script.
- "Don't keep activities" in developer options is a useful manual check, but it destroys Activities, not the process, so it doesn't replace this.
If your team has never seen a process-death bug, it's probably not because there aren't any. Users just report them as "the app forgot what I was doing".
The script is scripts/device_checks.py in the repository.