The lifecycle callbacks
Think of a theater stage. An actor is hired (created), walks onto the stage (started, visible), and steps into the spotlight (resumed, the one the audience interacts with). Then they step out of the spotlight, walk off stage, and eventually leave the theater. Android tells your Activity at each of those moments by calling one of its methods.
The lifecycle is a state machine (INITIALIZED → CREATED → STARTED → RESUMED and back down to DESTROYED) exposed through callbacks and, more usefully, through Lifecycle observers and lifecycle.currentState. The practical rules: pair start/stop at the same level (onStart/onStop for “while visible”, onResume/onPause for “while in front”), keep onPause fast (the next Activity waits for it), and never assume onDestroy runs (process death skips it).
Activity lifecycle
Tap a box to see what it does.
Created
Set up the UI (setContent), get the ViewModel, restore saved state. Not visible yet.
Started
Visible, maybe behind a dialog or in split screen. Start work that should run only while the user can see the screen (location for a map, live updates); stop it in onStop.
Resumed
In front, receiving input. Exclusive things like the camera preview start here and stop in onPause. Keep onPause short: the next screen waits for it.
Stopped
In the background (another app or Activity is in front). Still in memory, but Android may kill the whole process at any time without calling anything else.
Destroyed
Finished, or being recreated for a configuration change (rotation, dark mode, language). Not called at all if the process is killed.
Process killed
Android frees memory by killing background apps. When the user returns, the Activity is recreated from saved state; everything else in memory is gone.
class MapActivity : ComponentActivity() {
private val viewModel: MapViewModel by viewModels()
private lateinit var locationClient: LocationClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // set up once
locationClient = LocationClient(this)
setContent { MapScreen(viewModel) }
}
override fun onStart() {
super.onStart() // now visible: start live location
locationClient.start { viewModel.onLocation(it) }
}
override fun onStop() {
locationClient.stop() // not visible anymore: stop it
super.onStop()
}
}public class MapActivity extends AppCompatActivity {
private MapViewModel viewModel;
private LocationClient locationClient;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // set up once
setContentView(R.layout.activity_map);
viewModel = new ViewModelProvider(this).get(MapViewModel.class);
locationClient = new LocationClient(this);
}
@Override protected void onStart() {
super.onStart(); // now visible: start live location
locationClient.start(viewModel::onLocation);
}
@Override protected void onStop() {
locationClient.stop(); // not visible anymore: stop it
super.onStop();
}
}The rule that prevents most lifecycle bugs: whatever you start in one callback, stop in its partner. onStart ↔ onStop, onResume ↔ onPause. Location, sensors, live data from a server and animations usually belong to onStart/onStop: they run while the user can see the screen and stop when they can’t.
Predict the output
Activity A is on screen. The user taps a button that opens Activity B, then presses Back. Which order of A's callbacks is right?
When B opens, A gets onPause (B's onCreate/onStart/onResume run next) and then onStop once B covers it. A is not destroyed: it stays in memory, stopped. On Back, B pauses, then A gets onRestart, onStart, onResume, and finally B is stopped and destroyed. Option 3 would be right only if B were transparent or a dialog-style Activity, which leaves A visible (paused, not stopped).
Practice: log it yourself
- Override every callback in a test Activity and log its name:
Log.d("Lifecycle", "onStart"). - Open another screen, press Back, press Home, come back, rotate the phone, switch dark mode.
- Watch Logcat filtered by “Lifecycle”. Rotation and dark mode show a full onDestroy → onCreate: the Activity is recreated.
- In Developer options, turn on Don’t keep activities and repeat: every Activity is destroyed as soon as it’s in the background, a quick way to find state that’s lost.
Lifecycle-aware components
Overriding callbacks in every Activity scales poorly. LifecycleObserver/DefaultLifecycleObserver let a component manage itself (lifecycle.addObserver(locationClient) with onStart/onStop inside it), and in Compose, LifecycleStartEffect / LifecycleResumeEffect and collectAsStateWithLifecycle() tie work to the host lifecycle without touching the Activity. Prefer these over callbacks in screens: the start/stop pair lives in one place, and it’s reusable across Activities and Fragments.
- States: CREATED → STARTED (visible) → RESUMED (focused, interactive), mirrored on the way down. Pair work at the same level:
onStart/onStopfor visibility,onResume/onPausefor focus (camera, exclusive resources). KeeponPausecheap. - A started-but-not-resumed Activity is visible (multi-window, dialogs, transparent activities on top).
onDestroyisn’t guaranteed (process death). UseDefaultLifecycleObserver,LifecycleStartEffect,collectAsStateWithLifecycleinstead of per-Activity overrides.
Check this part
Where should live location updates for a map screen start and stop?
Activity A opens Activity B. What happens to A?
Why can't cleanup rely on onDestroy?