Zygote
Components & Intentsintermediate

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.

11 minAPI 1–37 · 17interactivequizverified 2026-10-27

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, plus onRestart when coming back from stopped.
  • Configuration change destroys and recreates the instance; a survives, the does not.
  • death destroys everything. Only the saved Bundle and survive.
  • onSaveInstanceState runs 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#

Loading diagram…
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.
    }
}
CallbackGuaranteed to be followed byUse it for
onCreateonStartone-time setup, view binding, state restoration
onStartonResume or onStopregistering visible-only listeners
onResumeonPausestarting interactive resources
onPauseonResume or onStopstopping things that must not run unfocused
onStoponRestart or onDestroyreleasing heavy resources
onDestroynothingfinal 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 changeProcess death
Activity instancedestroyed and recreateddestroyed
ViewModelsurvivesgone
In-flight coroutines in viewModelScopekeep runninggone
Bundle from onSaveInstanceStateused by the new instanceused by the new process
Singleton objectssurvivegone
Files, database, DataStoresurvivesurvive

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 onPause if 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.
  • onStop is 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#

Interface changes16 releases
APIVersionWhat changed
11.0
  • Pull-down notification shade
  • Home screen with widgets
31.5 Cupcake
  • Home screen widgets and folders
92.3 Gingerbread
  • New UI with dark theme and green accents
113.0 Honeycomb
  • The Holo design language arrives
123.1 Honeycomb
  • Resizable widgets
164.1 Jelly Bean
  • Project Butter: vsync-driven, smooth UI
  • Expandable notifications
204.4W KitKat (Wear)
  • Voice-driven card UI
215.0 Lollipop
  • Material Design
  • Notifications on the lock screen
247.0 Nougat
  • Quick Settings tiles
  • Direct Reply in notifications
268.0 Oreo
  • Notification channels
289 Pie
  • Dark theme groundwork
2910
  • System-wide dark theme
3011
  • Conversation notifications and bubbles
  • Media controls in Quick Settings
3112
  • Material You dynamic colour
3313 Tiramisu
  • Themed app icons
  • Runtime notification permission
3616 Baklava
  • Material 3 Expressive
  • Live Updates notifications

The trajectory: fewer reasons to destroy an activity, more places to put state that survives when it happens anyway.

Under the hood#

Curiosity corner#

  • onDestroy is not guaranteed. If the process is killed, no callback runs. Cleanup that must happen belongs in a finally or 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#

Force a configuration change

adb shell cmd activity restart-activity com.example.notes/.EditorActivity

Watch lifecycle transitions

adb logcat -s ActivityTaskManager:I ActivityManager:I | grep -i "notes"

Check yourself#

Check yourself

1 / 12

You call `startActivity()` for an activity in another app. Which check does the system perform before delivering the intent?

0 answered · 0 correct

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.

  1. docsBehavior changes: Apps targeting Android 16 or higherdeveloper.android.comaccessed 2026-10-27↩
  2. docsBehavior changes: Apps targeting Android 17developer.android.comaccessed 2026-10-27↩
  3. docsLifecycle of composablesdeveloper.android.comaccessed 2026-10-27↩
  4. docsProcesses and app lifecycledeveloper.android.comaccessed 2026-10-27↩
  5. docsThe activity lifecycledeveloper.android.comaccessed 2026-10-27↩
  6. docs活動簡介 | App architecturedeveloper.android.comaccessed 2026-10-27↩