The Activity lifecycle
Seven callbacks, two ways to die, and one Bundle. The Activity lifecycle is a contract about what the system may do to your screen while you are not looking.
An activity is destroyed when you rotate the phone. It is also destroyed when the user presses Back, when the system needs memory, and when a configuration changes that you did not opt out of. Those look similar in a log, and they mean completely different things for your state.
TL;DR#
- The lifecycle has six callbacks you will use:
onCreate,onStart,onResume,onPause,onStop,onDestroy, plusonRestartwhen coming back fromstopped. - Configuration change destroys and recreates the instance; a survives, the does not.
- death destroys everything. Only the saved
Bundleand survive. onSaveInstanceStateruns before a stop that could outlive the process — not before every stop.- Since Android 16, some configuration changes no longer restart activities by default; Android 17 extends that list.12
Mental model#
Think of a shop on a high street.
onCreate is the day you fit out the shop: you build the counters, stock the shelves, put up the
sign. onStart is unlocking the door. onResume is turning the sign to Open.
onPause is when a customer steps in front of the window and you cannot be seen — still there, not
fully available. onStop is closing for the night: the lights go off but the stock stays on the
shelves. onDestroy is the day the lease ends and the fittings are removed.
Where the analogy breaks#
The shop does not get rebuilt overnight while you sleep, and Android does. Rotation, a theme change
or a font-size change can tear the whole place down and rebuild it from a photograph of the shelves.
The photograph is the saved-state Bundle, and it holds far less than the real shop. Learning what
belongs in it — and what belongs in a ViewModel instead — is most of the practical skill.
The big picture#
Text description of this diagram
An interactive Activity lifecycle state machine. Buttons send events: open, home, back, rotate, show a dialog, dismiss it, start a second activity, return from it, enter and leave split screen, kill the process, and relaunch. The diagram highlights the current state — not running, created, started, resumed, paused, stopped or destroyed — and the log records every callback the system would invoke, in order. A saved-state panel shows what was written to the Bundle at onSaveInstanceState and whether a later instance restored it. A Don't keep activities toggle mirrors the developer option that destroys an activity as soon as it is invisible, so the difference between a stop and a destroy is visible.
Rotate after typing something: the log shows the full destroy-and-recreate sequence, and the saved
state panel shows the bundle that carried your draft across. Then press Home, come back: no
onCreate, just onRestart — the instance never died.
Deep dive#
The callbacks, and what each one is for#
class EditorActivity : ComponentActivity() {
private val viewModel: EditorViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Build the UI, restore from ViewModel + savedInstanceState.
setContent { EditorScreen(viewModel) }
}
override fun onStart() {
super.onStart()
// The activity is becoming visible. Register visible-only observers here.
}
override fun onResume() {
super.onResume()
// Visible AND interactive. Start camera preview, sensors, animations.
}
override fun onPause() {
super.onPause()
// Losing focus. Pause things that must not run behind a dialog.
}
override fun onStop() {
super.onStop()
// No longer visible. Release expensive resources.
}
override fun onDestroy() {
super.onDestroy()
// The instance is going away — for a rotation or for good.
}
}| Callback | Guaranteed to be followed by | Use it for |
|---|---|---|
onCreate | onStart | one-time setup, view binding, state restoration |
onStart | onResume or onStop | registering visible-only listeners |
onResume | onPause | starting interactive resources |
onPause | onResume or onStop | stopping things that must not run unfocused |
onStop | onRestart or onDestroy | releasing heavy resources |
onDestroy | nothing | final cleanup, cancelling scoped work |
The important asymmetry is between onPause and onStop. onPause is short and must be fast: the
next activity cannot resume until it returns. onStop is where expensive teardown belongs.
Two deaths#
The distinction that matters most:
| Configuration change | Process death | |
|---|---|---|
| Activity instance | destroyed and recreated | destroyed |
ViewModel | survives | gone |
In-flight coroutines in viewModelScope | keep running | gone |
Bundle from onSaveInstanceState | used by the new instance | used by the new process |
| Singleton objects | survive | gone |
| Files, database, DataStore | survive | survive |
That table is the whole architecture argument in miniature. Anything that must survive rotation goes
in a ViewModel. Anything that must survive process death goes in saved state or on disk — and
saved state is small, because it is a Bundle crossing a Binder transaction.
What onSaveInstanceState is actually for#
It runs when the system believes the activity may be killed while stopped. That is not every stop: if the activity is finishing, or if the system has no reason to expect a return, it may be skipped.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(KEY_DRAFT, binding.editor.text.toString())
outState.putInt(KEY_SCROLL, binding.list.computeVerticalScrollOffset())
}Two rules follow from the size limit. First, save identifiers and small values, not lists. Second,
prefer SavedStateHandle in the ViewModel, which writes into the same bundle but keeps the logic out
of the activity:
class EditorViewModel(private val handle: SavedStateHandle) : ViewModel() {
var draft: String
get() = handle["draft"] ?: ""
set(value) { handle["draft"] = value }
}SavedStateHandle is included automatically when the ViewModel is created by viewModels(), which is
why it is the recommended place for state that must survive both rotation and process death.
What "visible" actually means#
The lifecycle callbacks describe visibility, but the definition changed with multi-window. Before Android 10, at most one activity was resumed system-wide. Since multi-resume, several can be resumed at once, and a paused activity can still be fully visible.
Three practical consequences:
- Do not stop work in
onPauseif it is still visible. A video in split screen is paused when it loses focus but should keep playing. - Lifecycle-aware components know the difference.
repeatOnLifecycle(STARTED)continues while the activity is visible even if paused. onStopis the reliable "not visible" signal. Use it for releasing cameras and unregistering listeners that must not run unseen.
Configuration changes are shrinking#
Historically, almost any configuration change destroyed the activity: rotation, keyboard, dark mode, font scale, locale. Each one was a chance to lose state.
Android 16 changed the default for several of them: activities no longer restart for keyboard,
navigation, touchscreen and colour-mode changes, and android:recreateOnConfigChanges opts back
in.2 Older releases had already made configChanges a manifest
escape hatch:
<activity
android:name=".PlayerActivity"
android:configChanges="orientation|screenSize|keyboardHidden" />Declaring configChanges means "do not recreate me; call onConfigurationChanged instead". It is
right for a video player that would otherwise restart playback. It is wrong as a general strategy,
because you then own every layout decision for every configuration.
Compose changes the shape, not the contract#
A Compose UI lives inside an activity, so the activity lifecycle still runs — but composables have
their own lifecycle inside the composition. LaunchedEffect, DisposableEffect and
rememberCoroutineScope tie work to entering and leaving the composition, not to the activity
callbacks.
@Composable
fun EditorScreen(viewModel: EditorViewModel) {
val state by viewModel.state.collectAsStateWithLifecycle()
LaunchedEffect(state.documentId) {
viewModel.load(state.documentId)
}
}collectAsStateWithLifecycle() is the bridge: it collects only while the surrounding lifecycle is at
least STARTED, so a Flow stops emitting to a UI that is not visible.3
That is the Compose-era equivalent of unregistering a listener in onStop.
Retained instances, and why they were replaced#
Before ViewModel, the way to survive rotation was setRetainInstance(true) on a fragment, or
onRetainNonConfigurationInstance on an activity. Both kept an object across the destroy/recreate pair.
They worked and they were traps:
- A retained object held a reference to the destroyed activity's views unless the developer was careful.
- The lifecycle of a retained fragment was not the same as a normal fragment's, so code that worked in one configuration failed in another.
- There was no scoping: everything retained lived for the whole activity, whether or not it needed to.
ViewModel solved all three by making the retention explicit, scoped, and free of a view reference. It
is one of the clearest examples in Android of a library replacing a platform feature because the
platform feature's semantics were too subtle to use safely.
The Bundle is smaller than it looks#
onSaveInstanceState writes into a Bundle that the system holds outside your process, which
means it is parcelled and subject to the Binder transaction limit. In practice that is around a
megabyte shared with everything else in the transaction, and a bitmap or a long list will exhaust it.
Three things that do not belong there: images, lists of domain objects, and anything that can be re-read from disk. Three that do: an identifier, a scroll position, a draft string. If the value can be recomputed, recompute it — the bundle is a postcard, not a suitcase.
Process lifecycle is not activity lifecycle#
The system decides whether your process lives based on the most important component in it. An app with a visible activity is foreground; an app with only a cached activity is a candidate for killing.4 That is why an activity being stopped says nothing about whether your process will be alive in ten minutes — and why the saved bundle is the only promise you get.
How it evolved#
| API | Version | What changed |
|---|---|---|
| 1 | 1.0 |
|
| 3 | 1.5 Cupcake |
|
| 9 | 2.3 Gingerbread |
|
| 11 | 3.0 Honeycomb |
|
| 12 | 3.1 Honeycomb |
|
| 16 | 4.1 Jelly Bean |
|
| 20 | 4.4W KitKat (Wear) |
|
| 21 | 5.0 Lollipop |
|
| 24 | 7.0 Nougat |
|
| 26 | 8.0 Oreo |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 33 | 13 Tiramisu |
|
| 36 | 16 Baklava |
|
The trajectory: fewer reasons to destroy an activity, more places to put state that survives when it happens anyway.
Under the hood#
- Activity.javathe lifecycle methods and their documented ordering
- ActivityThread.javaperformCreate, performStart, performResume — the dispatcher
- the transaction classes system_server uses to drive lifecycle transitions
- ActivityTaskManagerService.javathe policy that decides when to destroy and recreate
Curiosity corner#
onDestroyis not guaranteed. If the process is killed, no callback runs. Cleanup that must happen belongs in afinallyor in a lifecycle-aware scope.- The saved bundle has a hard size limit shared with the Binder transaction buffer, which is why
saving a bitmap there throws
TransactionTooLargeException. - Rotation is not the main cause of recreation any more. Multi-window resizes, foldable posture changes and font-scale changes all recreate activities on many devices.
Try it#
adb shell cmd activity restart-activity com.example.notes/.EditorActivity
adb logcat -s ActivityTaskManager:I ActivityManager:I | grep -i "notes"
Check yourself#
Check yourself
You call `startActivity()` for an activity in another app. Which check does the system perform before delivering the intent?
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsBehavior changes: Apps targeting Android 16 or higherdeveloper.android.comaccessed 2026-10-27↩
- docsBehavior changes: Apps targeting Android 17developer.android.comaccessed 2026-10-27↩
- docsLifecycle of composablesdeveloper.android.comaccessed 2026-10-27↩
- docsProcesses and app lifecycledeveloper.android.comaccessed 2026-10-27↩
- docsThe activity lifecycledeveloper.android.comaccessed 2026-10-27↩
- docs活動簡介 | App architecturedeveloper.android.comaccessed 2026-10-27↩