← Back to android roadmap

Process Death vs Activity Recreation, and How Saved State Really Works

Why your screen forgets everything after an hour in the background, what survives which event, and how SavedStateHandle, rememberSaveable, Bundle, Parcelable, Serializable and Kotlin Serialization fit together. Read from the AndroidX source and Google's guidance.

  • Android
  • Saved state
  • Process death
  • Parcelable
  • Internals
On this page
  1. What you’ll study
  2. The basics
  3. The high level
  4. Checkpoint
  5. The low level
  6. Why it helps
  7. How to test process death
  8. The map
  9. Edge cases
  10. Cross questions
  11. Revision: key points
  12. The fine print

What you’ll study

  1. Recreation vs process death vs the user dismissing a screen.
  2. What survives which event, and the three layers of state.
  3. Where saved state goes: the Bundle the system keeps for you.
  4. When the Bundle is written and when it is restored.
  5. SavedStateHandle and rememberSaveable.
  6. Bundle, Parcel, Parcelable, Serializable and Kotlin Serialization.
  7. The size limit, and how to test process death.

How it connects

The three events differ in one thing: does the process survive, and does the user expect to come back? Recreation keeps the process, so memory (the ViewModel) carries state across. Process death loses the process but the user expects to return, so only the state the system kept for you, the Bundle, comes back. Dismissal keeps nothing. SavedStateHandle, rememberSaveable, Fragments and Navigation all write into that one Bundle, which is why it must be small and parcelable, and why Parcelable vs Serializable matters at all.

Read it at your level

Level Read You should be able to
Student The basics, the survival table, the demo Tell recreation, process death and dismissal apart
Fresher + How to test process death Reproduce process death with adb and Recents
Junior + The low level 2–3, Why it helps Choose between SavedStateHandle, rememberSaveable and a database; stop losing form input
Mid + The low level 1 and 4–6, The map Explain the registry, the allowed types, Parcelable vs Serializable vs Kotlin Serialization, and the size limit
Senior + Edge cases, cross questions Design around save timing, navigation-argument size and singletons after restore

The basics

There are three different ways a screen can be destroyed, and they are easy to mix up:

  1. Recreation (configuration change). The user rotates the phone or changes the theme. Android destroys the Activity and immediately creates a new one, in the same process.
  2. Process death (system-initiated). Your app is in the background and the system needs memory, so it kills the whole process. Later the user comes back from Recents, and Android recreates the screen as if nothing happened, in a brand-new process.
  3. The user dismisses it. They press Back to leave, swipe the app away from Recents, or you call finish(). Nothing is expected to come back.

The second one is the trap. The user didn’t leave; they expect the half-filled form, the selected tab and the scroll position to still be there. But your process, your objects and your ViewModels are gone. Only what you saved comes back.

The high level

What survives which event

This is Google’s own comparison, extended with the Compose and field cases:

Where the value lives Rotation Process death User dismisses
Activity field, remember { } ✗ ✗ ✗
ViewModel field ✓ ✗ ✗
rememberSaveable, SavedStateHandle, onSaveInstanceState ✓ ✓ ✗
Database, DataStore, files, network ✓ ✓ ✓

So the rule of thumb is three layers:

  • ViewModel for state you can rebuild but don’t want to reload on every rotation.
  • Saved state (SavedStateHandle, rememberSaveable) for the small things the user would notice missing: ids, selections, input, scroll position.
  • Persistent storage for real data, plus anything the user would be upset to lose even after closing the app.

Where saved state actually goes

your app process                          system_server
────────────────                          ─────────────
SavedStateHandle ─┐
rememberSaveable ─┼─► SavedStateRegistry ─► Bundle ═Binder═► kept with
Navigation stack ─┤   (one key per          │               the Activity
Fragments, Views ─┘    component)           │               record, even
                                            │               if your
        restored in onCreate() ◄════════════╧══════════     process dies

When your Activity goes to the background, every component writes its state into one Bundle. The Bundle is flattened into a Parcel and sent to the system over Binder, where it is kept outside your process. If the process dies, the next onCreate(savedInstanceState) gets that Bundle back.

Interactive demo: put a value in six places, then rotate, let the system kill the process, or dismiss the app, and see which values come back. Also try saving a list that is too big. Needs JavaScript.

Checkpoint

Before going deeper, you should be able to say:

  • Recreation keeps the process; process death doesn’t; dismissal keeps nothing.
  • A ViewModel survives only recreation; saved state also survives process death; disk survives everything.
  • All saved state goes into one Bundle that the system keeps for you.

Run the “what survives?” demo through all three events before reading on.

The low level

1. When the Bundle is written

  • onSaveInstanceState(outState) runs when the Activity may be killed later: going to the background, or before a configuration change. For apps targeting Android 9 (API 28) and higher it is always called after onStop().
  • It is not called when the user dismisses the screen (Back, finish()), because nothing will be restored.
  • ComponentActivity.onSaveInstanceState calls savedStateRegistryController.performSave(outState). The registry asks every registered SavedStateProvider for its state and stores all of it under one key: androidx.lifecycle.BundlableSavedStateRegistry.key.
  • Restore happens in onCreate(): performRestore() must run before the owner is STARTED (the source checks this), so every component reads its state back before the UI is shown.

2. SavedStateHandle: a map that writes itself into the Bundle

class SearchViewModel(private val handle: SavedStateHandle) : ViewModel() {
    // A StateFlow backed by the saved map
    val query: StateFlow<String> = handle.getStateFlow("query", "")
    fun onQuery(q: String) { handle["query"] = q }
}
  • The handle is created by createSavedStateHandle() from the factory’s CreationExtras, keyed by the ViewModel’s key. All handles of an owner are collected by one provider registered as androidx.lifecycle.internal.SavedStateHandlesProvider.
  • It starts with the default arguments: the Activity’s intent extras, or the Navigation destination’s arguments. That’s why nav args are readable from the handle.
  • It only accepts what a Bundle can hold. The Android allow-list in the source is: primitives and their arrays, String, CharSequence, Bundle, Binder, ArrayList, Parcelable, Serializable, SparseArray, Size, SizeF. Anything else throws "Can't put value with type … into saved state".
  • With Kotlin Serialization you can store any @Serializable type through a delegate: var filter by handle.saved { Filter() }.

3. rememberSaveable: the Compose side

  • Each rememberSaveable call is keyed by its position in the composition (the composite key hash), so it comes back to the same call site, even inside list items if you give them stable keys.
  • The values go to a SaveableStateRegistry, which on Android is connected to the same SavedStateRegistry, so they end up in the same Bundle.
  • canBeSaved() allows only Bundle-compatible values. For anything else, provide a Saver (listSaver, mapSaver), make the class @Parcelize, or use rememberSerializable with a @Serializable class.

4. Bundle, Parcel, Parcelable, Serializable and Kotlin Serialization

These are layers, not alternatives:

your object
   │  how do I turn it into bytes?
   ├─ Parcelable ............ writes its fields into a Parcel itself
   │                          (@Parcelize generates the code)
   ├─ java.io.Serializable .. Java serialization by reflection,
   │                          the bytes are then written into the Parcel
   └─ @Serializable ......... Kotlin Serialization; AndroidX encodes it
                              straight into a SavedState (Bundle) structure
   ▼
Bundle      a typed key → value map
   ▼
Parcel      Binder's flat byte container
   ▼
Binder      the transaction to system_server (1 MB buffer per process)
  • Parcel is Android’s IPC wire format: fast, flat, not meant for disk.
  • Parcelable is the Android way to put an object in a Parcel: you (or @Parcelize) write each field in order, with no reflection. It’s the recommended choice for saved state.
  • java.io.Serializable works in a Bundle too, but uses reflection and produces larger output. Fine for a small value, slow and big for anything else.
  • Kotlin Serialization (@Serializable) generates serializers at compile time and works across Kotlin Multiplatform. AndroidX can encode such objects directly into saved state (encodeToSavedState, SavedStateHandle.saved, rememberSerializable).

5. Size: the limit that crashes apps

Google’s guidance, from the Parcelables and Bundles page:

  • The Binder transaction buffer is 1 MB, shared by every transaction in your process at that moment, not per Activity.
  • The system holds your saved state for as long as the user can navigate back to that Activity, even after your process is killed.
  • Keep saved state under 50 KB.
  • From Android 7.0, going over throws TransactionTooLargeException at runtime. Before that, it only logged a warning.

So never save a list of items, a bitmap or a large response. Save the id or the query, and reload the data from your repository.

6. What else is restored, and what isn’t

  • Restored: the task and back stack, every Activity’s saved Bundle, the intent each one was started with, Fragments (the FragmentManager re-adds them), Views with an android:id (EditText text, scroll positions), Navigation’s back stack.
  • Not restored: singletons and objects (they start fresh, because Application.onCreate() runs again), in-memory caches, ViewModels, running coroutines, anything in a companion object.
  • Only the top Activity is recreated at first. Activities below it are recreated from their Bundles when the user navigates back to them.

Why it helps

  • “Crash after returning to the app: lateinit not initialized.” A singleton or static field set on an earlier screen is empty in the new process. Read what you need from saved state or storage, not from memory set by another screen.
  • “The form was empty when I came back.” The input lived in a ViewModel field or remember. Move it to SavedStateHandle or rememberSaveable.
  • “TransactionTooLargeException when the app goes to the background.” Something saved a big object: a list in SavedStateHandle, a large nav argument, a Fragment’s arguments. Save ids instead.
  • “Fragments duplicated after rotation.” You add the fragment in onCreate() every time. Only add it when savedInstanceState == null; the FragmentManager already restored it.
  • “Works in testing, broken in production.” Developers rarely see process death because their phones have memory to spare. Test it on purpose (below).

How to test process death

  1. Open the screen, change some state, and press Home so the app is in the background.
  2. Kill it the way the system does:
    • adb shell am kill <your.package> (it only kills a background process), or
    • Android Studio’s Terminate application button in Logcat, or
    • revoke a permission in Settings, which kills the process too.
  3. Reopen the app from Recents, not the launcher icon.

The developer option Don’t keep activities destroys Activities but keeps the process, so it tests saved state, not the loss of singletons and ViewModels. Use it as a quick check, not as the real test.

The map

Idea Where it lives
Save and restore hooks Activity.onSaveInstanceState, onCreate(savedInstanceState), onRestoreInstanceState
One registry for all components SavedStateRegistry, SavedStateRegistryController.performSave/performRestore, SavedStateProvider
The key in the Bundle androidx.lifecycle.BundlableSavedStateRegistry.key
ViewModel saved state SavedStateHandle, createSavedStateHandle(), SavedStateHandlesProvider, getStateFlow
Allowed types ACCEPTABLE_CLASSES in SavedStateHandleImpl.android.kt
Compose rememberSaveable, SaveableStateRegistry, Saver, rememberSerializable
Kotlin Serialization androidx.savedstate.serialization (encodeToSavedState, decodeFromSavedState), SavedStateHandle.saved
Wire format Bundle, Parcel, Parcelable, @Parcelize, java.io.Serializable
Size limit Binder transaction buffer, TransactionTooLargeException

Edge cases

  • Swiping from Recents is not process death. It’s the user dismissing the task: saved state is thrown away and the app starts fresh next time.
  • Low-memory kill while the app is visible. Rare, but the foreground app can also be killed. Nothing is restored for a screen whose state was never saved.
  • SavedStateHandle writes after onSaveInstanceState. Values set after the state was saved are only captured at the next save. Save input as it changes, not at the last moment.
  • Custom Parcelable across apps. Don’t put custom Parcelables in intents sent to other apps; their class loader can’t read your class.
  • Huge navigation arguments. Nav args are saved with the back stack. Pass ids, not objects.
  • rememberSaveable in LazyColumn items. Without stable item keys, saved state can come back on the wrong item after the list changes.
  • Process death during a running job. In-flight network calls and coroutines are gone. Make the operation resumable, or hand it to WorkManager.

Cross questions

What is the difference between configuration change recreation and process death?

On recreation the process stays alive, so in-memory objects like ViewModels are handed to the new Activity. On process death the process is gone, so only state the system holds for you (the saved Bundle) or state on disk comes back.

Is onSaveInstanceState() called when the user presses Back?

No. It is called when the Activity might be killed and recreated later: going to the background, or a configuration change. On Back or finish() nothing will be restored, so nothing is saved.

Where is the saved Bundle stored while my process is dead?

In the system process, attached to your Activity's record. It is sent there over Binder when the state is saved, which is why it has to be a small, parcelable Bundle.

Why does SavedStateHandle reject my data class?

It only accepts Bundle-compatible types. Make the class @Parcelize (or Serializable), or with Kotlin Serialization use handle.saved { }.

Parcelable or Serializable for saved state? Why?

Parcelable. It writes fields directly into the Parcel with no reflection, so it is faster and smaller. java.io.Serializable works but uses reflection. For Kotlin Multiplatform code, Kotlin Serialization with the AndroidX saved-state encoder is the modern option.

How big can saved state be?

Google recommends under 50 KB. The hard limit is the 1 MB Binder buffer shared by the whole process; from Android 7.0, exceeding it throws TransactionTooLargeException.

How would you test that a screen survives process death?

Background the app, kill it with adb shell am kill or Studio's Terminate button, then reopen it from Recents. "Don't keep activities" is not enough: it keeps the process, so singletons and caches survive.

After process death, does the ViewModel's init run again?

Yes. It's a new ViewModel in a new process. That's why init should read its inputs (ids, query) from SavedStateHandle and reload the data.

What is the difference between remember, rememberSaveable and a ViewModel in Compose?

remember survives recomposition only. rememberSaveable also survives recreation and process death, through the saved Bundle. A ViewModel survives recreation (not process death), and lives as long as its owner, not the composable.

Revision: key points

  1. Recreation (same process) is not process death (new process, user returns), and neither is dismissal (nothing returns).
  2. Three layers: ViewModel for rotation, saved state for process death, storage for everything.
  3. onSaveInstanceState() runs after onStop() for apps targeting API 28+, and never on Back or finish().
  4. Everything goes into one Bundle through SavedStateRegistry, sent over Binder to the system.
  5. SavedStateHandle accepts only Bundle types; use @Parcelize or handle.saved { } for your own.
  6. rememberSaveable is keyed by its position in the composition; custom types need a Saver.
  7. Parcelable writes fields directly (fast); Serializable uses reflection; Kotlin Serialization works across platforms.
  8. Keep saved state under 50 KB: the 1 MB Binder buffer is shared, and Android 7.0+ throws TransactionTooLargeException.
  9. Save ids, not lists, and reload the data after restore.
  10. Test with Home, then adb shell am kill, then reopen from Recents; “Don’t keep activities” is not enough.

Then close the page and try the cross questions from memory.

The fine print

The AndroidX pieces are from the current source (October 2026); the limits and timings are from Google’s Android developer documentation. The 1 MB buffer is described as “currently” 1 MB, and device behaviour under memory pressure varies, so design for small saved state and test on real low-memory devices.

This is the last part of the series for now: why read internals, launch modes, WorkManager, ViewModel.