App Lifecycle and Process Death
Every Android app runs inside a process that the operating system can create, pause, or kill without warning. The Activity lifecycle (onCreate, onStart, onResume, onPause, onStop, onDestroy) tells you when your UI is visible and interactive, but it does not tell the whole story: the OS can terminate your entire process while it is in the background to reclaim memory for other apps, and your app must be able to reconstruct its UI state as if nothing happened when the user returns. This is called process death, and it is one of the most commonly mishandled aspects of Android development because it rarely happens during casual manual testing but happens constantly on real, memory-constrained devices.
Cricket analogy: A batsman's innings can look continuous, but the umpire's over-count doesn't reveal that the groundstaff could halt play for rain at any moment, wiping the pitch conditions the batsman had adjusted to — just as the OS can kill your process mid-background without the Activity lifecycle warning you.
Configuration Changes vs. Process Death
It is important to distinguish two very different events that both destroy and recreate an Activity. A configuration change, such as a screen rotation, a theme toggle, or a font-scale change, destroys and immediately recreates the same Activity instance's UI within the same still-running process; in-memory state kept in a ViewModel survives automatically because ViewModel instances are retained across configuration changes by the Android framework. Process death, by contrast, is the OS killing the entire process while the app is backgrounded; when the user navigates back, Android creates a brand-new process and a brand-new ViewModel, so anything that lived only in memory is gone. Only persisted state — written to a Bundle via onSaveInstanceState, to SavedStateHandle, or to disk — survives process death.
Cricket analogy: A rain delay that pauses and resumes the same match (same players, same scoreboard) is like a configuration change — the ViewModel-equivalent scoreboard persists; but if the whole tournament were abandoned and restarted next season with new squads, that's process death — only recorded stats (persisted state) carry over.
SavedStateHandle in ViewModel
The modern, Compose-friendly way to survive process death is SavedStateHandle, which is automatically injected into a ViewModel by the ViewModel-ktx / Hilt integration. Values placed into SavedStateHandle are serialized into the same Bundle mechanism used by onSaveInstanceState, so they are restored even after the OS has killed and recreated the process. Because SavedStateHandle only supports Bundle-compatible types (primitives, Parcelable, Serializable), it should be reserved for small, essential UI state — such as the current tab index, a search query, or a scroll position — not entire domain objects, which belong in a repository or database instead.
Cricket analogy: A scorer jotting the current over, batsman's score, and required run rate onto a small card before leaving the box — not the entire ball-by-ball commentary — mirrors SavedStateHandle: save only the essential small facts (like tab index or scroll position), not the whole match archive.
class SearchViewModel(
private val savedStateHandle: SavedStateHandle,
private val repository: SearchRepository
) : ViewModel() {
// Survives configuration changes AND process death.
val query: StateFlow<String> = savedStateHandle.getStateFlow("query", "")
val results: StateFlow<List<SearchResult>> = query
.debounce(300)
.filter { it.length >= 2 }
.flatMapLatest { repository.search(it) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList()
)
fun onQueryChanged(newQuery: String) {
savedStateHandle["query"] = newQuery
}
}You can simulate process death without waiting for the OS to reclaim memory. On a device or emulator with Developer Options enabled, turn on "Don't keep activities," then background your app — Android destroys the process the instant you leave it, letting you test restoration behavior on every single navigation.
A common mistake is treating onDestroy() as a reliable place to save critical data. onDestroy() is not guaranteed to be called before process death — the OS can kill a background process without invoking any further lifecycle callbacks at all. State that must survive should be saved proactively (e.g., in onPause() via onSaveInstanceState, or continuously via SavedStateHandle/StateFlow), not deferred to onDestroy().
Beyond individual screens, Android ranks processes by importance to decide which to kill first when memory is low: a foreground process hosting a visible, actively interacting Activity has the highest priority and is almost never killed; a visible process (partially obscured, e.g. behind a dialog) is next; a background/cached process, whose Activities have all called onStop(), is the most likely to be killed, and can be terminated with no further callbacks. Long-running work that must survive backgrounding — network sync, uploads — should not rely on the Activity or process staying alive at all, and should instead be scheduled through WorkManager, which is independent of the hosting Activity's lifecycle.
Cricket analogy: The team currently batting on the field (foreground) is protected and never substituted mid-over; a team warming up in the dugout (visible) is next in line; a team that's already finished its innings and gone to the pavilion (background/cached) is most likely to be released from the squad first.
- Configuration changes recreate the Activity within the same process; ViewModel state survives automatically.
- Process death kills the entire process; only Bundle-based or disk-persisted state survives.
- SavedStateHandle lets a ViewModel expose state as a StateFlow that automatically persists across process death.
- Test process death explicitly using the 'Don't keep activities' developer option — never assume it just because manual testing looked fine.
- onDestroy() is not a reliable place to persist state; save proactively, not reactively.
- Background work that must outlive the process (uploads, sync) belongs in WorkManager, not in Activity or ViewModel scope.
Practice what you learned
1. What is the key difference between a configuration change and process death?
2. Why does a ViewModel's in-memory state survive a screen rotation but not process death?
3. What type of data is appropriate to store in SavedStateHandle?
4. Why is it risky to save critical state only inside onDestroy()?
5. Which tool is best suited for work that must complete even if the app process is killed, like uploading a file?
Was this page helpful?
You May Also Like
ViewModel Basics
What Android's ViewModel class does, how it survives configuration changes, and how to create and scope ViewModels correctly in a Compose app.
Unidirectional Data Flow
A pattern where state flows down from a single source of truth to the UI, and events flow up to trigger state changes, keeping Compose UIs predictable and testable.
Lifecycle-Aware Coroutines
Lifecycle-aware coroutine APIs like repeatOnLifecycle and lifecycleScope tie coroutine execution to Android component lifecycle states, avoiding wasted work and crashes from stale UI updates.
stateIn and shareIn
stateIn and shareIn convert cold Flows into hot, shareable StateFlow and SharedFlow instances, with SharingStarted policies controlling exactly when the underlying producer runs.