Understanding Android Processes
How Android starts, hosts, and kills your app's process — and why it matters for state, memory, and crashes.
When you tap an app icon, Android doesn’t just “open the app.” It asks the system to start a process, load your code into it, and hand control to a component. Most lifecycle and state bugs become obvious once you can picture this layer.
What a process actually is
Each Android app runs in its own Linux process with a unique user ID. By default, all components of an app — activities, services, broadcast receivers — run in a single process on the main thread (also called the UI thread).
The process is created lazily: it doesn’t exist until something needs it, such as the user launching your launcher activity or the system delivering a broadcast.
// Every component you declare runs in the app's default process
// unless you opt into a separate one via android:process.
<service
android:name=".SyncService"
android:process=":sync" />
The leading colon (:sync) creates a private process named
com.example.app:sync. This isolates heavy or crash-prone work, at the cost of
extra memory and inter-process communication overhead.
The process lifecycle
Android ranks every running process by importance. When memory runs low, the system kills the least important processes first. From most to least important:
- Foreground — has an activity the user is interacting with, or a foreground service.
- Visible — has a visible (but not focused) activity.
- Service — running a started background service.
- Cached — no active components; kept around only to speed up relaunch.
Your app doesn’t get a callback when its process is killed in the background. The process simply disappears. This is why you can never rely on
onDestroy()being called.
What survives a process death
When the system kills a cached process and the user returns, Android creates a new process and tries to restore the user’s place. Knowing what is and isn’t preserved is critical:
- Saved instance state (
onSaveInstanceState) is restored — small UI state only. ViewModelis not restored; it lives only as long as the process.SavedStateHandlebridges the two: it survives process death like saved state.- Static fields and singletons are reinitialized from scratch.
class SearchViewModel(
private val state: SavedStateHandle
) : ViewModel() {
// Survives process death because it's backed by saved state.
var query: String
get() = state["query"] ?: ""
set(value) { state["query"] = value }
}
Testing process death
The easiest way to reproduce a killed process during development:
- Open your app, navigate somewhere with state.
- Press Home.
- In Android Studio, click Terminate Application (the red stop button), or run
adb shell am kill com.example.app. - Reopen the app from Recents.
If state is gone or the app crashes, you have a restoration bug.
Key takeaways
- A process is created on demand and can be killed at any time in the background.
- Process importance — not your code — decides what gets killed first.
- Persist anything the user would be upset to lose using
SavedStateHandleor disk. - Always test the “killed and restored” path; it’s where real-world crashes hide.