Android Lifecycle Tutorial: Key Callback Methods (oncreate onstart onresume)
Think of these three callbacks as the "entering the room" sequence. Each marks a clear, distinct state change your Activity moves through as it comes to the foreground. Your job is to hook your code into the right stage.
Visualizing the Lifecycle: The Room
Click the buttons below to simulate the lifecycle. Notice how the room builds up step-by-step.
1 onCreate: Initial Setup
This is for one-time construction. Inflate your layout, initialize non-UI components, and set up your ViewModel. Think: "What must exist before anything is visible?"
Pitfall: Heavy work in onCreate causes jank
Do not perform network calls, database queries, or large image decoding here. The system is waiting for onCreate() to finish before drawing the first frame. If you block it, your app feels sluggish or shows an "App isn't responding" (ANR) dialog.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ❌ Blocks UI thread
val data = fetchDataFromNetwork()
textView.text = data
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize ViewModel or adapter—no blocking operations.
// Start heavy work in onStart or use background threads.
}
2 onStart: Visible but not Foreground
The Activity is now visible—the user can see it—but it doesn't yet have input focus. A translucent system dialog (like volume control) or a semi-transparent Activity on top puts you here.
Pitfall: Assuming UI is fully visible
Do not assume the user can interact with your UI here. The window might be partially obscured. Avoid starting animations or camera previews here—save that for onResume().
3 onResume: Interactive and Foreground
This is the active state. Your Activity is in the foreground and receives all user input. This is where you start things that require the user's full attention: game loops, sensor listeners, camera preview.
Pitfall: Forgetting to re-register listeners
When onPause() runs (e.g., user gets a phone call), you must stop anything running. But when the user returns (onResume()), you must re-register them. Forgetting to do so leaves your app in a "paused" state even though it's visible again.
// Re-register listeners that were unregistered in onPause()
override fun onResume() {
super.onResume()
sensorManager.registerListener(
sensorListener,
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL
)
gameView.resumeLoop()
}
override fun onPause() {
super.onPause()
// Unregister to avoid battery drain and memory leaks
sensorManager.unregisterListener(sensorListener)
gameView.pauseLoop()
}
Professor's Mental Model:
onCreatebuilds the stage.onStartturns up the lights.onResumecues the actors.
Activity State Management Basics: Layers of Readiness
Think of an Activity's state not as a simple on/off switch, but as layers of readiness. You cannot be Resumed (fully active) without first being Started (visible), and you cannot be Started without being Created (initialized).
Visualizing the Layers
The system builds these layers in a strict order. Use the buttons to see how layers stack up, and how onPause() removes the top layer.
1 Strict Sequence
The system marches through CREATED → STARTED → RESUMED. Each step unlocks the next. You cannot be interactive (RESUMED) if the window isn't even visible (STARTED).
The Critical Pivot: onPause
This is the retreat. If you manage resources (like a camera) only in onStop, you risk leaving them running while the user is still looking at your screen but cannot interact. Always pair resource acquisition in onResume with release in onPause.
2 Managing Background Tasks
A background thread that outlives the RESUMED state can crash your app if it tries to update a destroyed UI. Tie your work to the lifecycle.
class MainActivity : AppCompatActivity() {
private var dataJob: Job? = null
override fun onResume() {
super.onResume()
// Start work when interactive
dataJob = lifecycleScope.launch {
fetchDataAndUpdateUi()
}
}
override fun onPause() {
super.onPause()
// CRITICAL: Stop work when focus is lost
dataJob?.cancel()
}
}
Professor's Mental Model:
onResumeis your "Go" signal.onPauseis your "Stop" signal.- States are layers. Move down (Pause → Stop → Destroy) just as deliberately as you move up.
Common Misconceptions About Lifecycle Events
Your mental model might be a straight line: onCreate → onStart → onResume → onPause → onStop → onDestroy. But in practice, the path can skip steps and loop back quickly. The most dangerous jump is: onPause can happen without onStop.
The "Shortcut" Path (Pause without Stop)
Click the buttons to simulate a "transient dialog" (like a volume popup). Notice how the app jumps from Resumed to Paused and back, completely skipping Stopped.
System Dialog
1 Misconception: "onPause means Hidden"
You might think onPause() means your Activity is no longer visible. Not necessarily. onPause() fires whenever your Activity loses foreground focus, even if it's still partially visible.
The "Transparent" Scenario
A semi-transparent system dialog (like volume control) or a permission popup appears. Your Activity is still visible underneath, but it's not interactive. The system calls onPause(). If the user dismisses the dialog immediately, onResume() is called next—onStop() never runs.
2 Pitfall: Assuming UI is Fully Stopped
If you treat onPause() as "the UI is gone" and release all resources here (like disconnecting a network client or destroying a heavy object), you'll pay a performance cost when onResume() has to rebuild them for a quick return.
The Sweet Spot
In onPause(), pause or release only what must not run while not in the foreground (camera, sensors, game loops). Keep lightweight, reinitializable resources (like a network client socket) if they're expensive to recreate and you expect a quick resume.
3 Reality: Rapid Pause-Resume Cycles
Because onPause() doesn't guarantee onStop(), your app must be ready for rapid pause‑resume cycles. This is common with:
- Transient system dialogs (volume, permissions)
- Multi-window mode (another app briefly covers yours)
- Picture-in-picture transitions
class MainActivity : AppCompatActivity() {
private var camera: Camera? = null
override fun onResume() {
super.onResume()
// Reacquire only if needed—fast operation
if (camera == null) {
camera = openCamera() // Should be quick; use cached instance if possible
}
camera?.startPreview()
}
override fun onPause() {
super.onPause()
// Release immediately—system may kill us after this
camera?.stopPreview()
camera?.release()
camera = null // Allow GC; will re-open on next onResume if needed
}
}
Professor's Mental Model:
onPause()is a quick pit stop, not the final exit.- Acquire foreground resources in
onResume(). - Release them in
onPause(). - Keep
onPause()lightweight—no heavy I/O.
Intuition Building: Visualizing the Lifecycle Flow
Think of the Activity lifecycle not as a static diagram you memorize, but as a dynamic flowchart—a map with multiple possible routes. The common linear drawing is a typical path, but the system can take shortcuts, skip steps, or loop back quickly. Your mental model must account for this fluidity.
The Timeline of Overlapping Zones
Instead of a ladder, picture nested zones. Resumed is inside Started, which is inside Created. Use the buttons to see how you can leave a zone without leaving the ones below it.
User opens volume control. You lose focus (Resumed → Paused) but remain Visible (Started).
User goes to Home screen. You lose visibility (Started → Stopped) but remain Created.
1 Misconception: The Diagram is Static
Beginners often treat the lifecycle diagram as a rigid sequence. It's not. The system does not guarantee onStop after onPause, nor onDestroy after onStop. Your code must work correctly if any callback is the last one you receive.
The Critical Shortcut
A transient dialog (like volume control) causes onPause(). If dismissed immediately, onResume() runs next—onStop() never runs. Your code must handle this gap.
2 Practical Impact: Where to Save Data?
Because the system can kill your process at any time after onPause(), you must treat onPause() as your "final chance" for critical saves.
override fun onStop() {
super.onStop()
// RISKY: If system kills after onPause(),
// this never runs. Data is lost.
saveUserProgressToPrefs()
}
override fun onPause() {
super.onPause()
// ✅ Safe: Guaranteed to run before potential kill.
// Save critical state here.
saveUserProgressToPrefs()
}
3 Pitfall: Ignoring onDestroy Cleanup
onDestroy is not guaranteed (the system can simply kill the process). You must not rely on it for releasing resources or saving data.
The Safe Zone
Only put cleanup in onDestroy that is safe to skip if the process dies—like closing file descriptors. The OS will reclaim memory anyway. For critical resources, release them in onStop or onPause.
override fun onStop() {
super.onStop()
// Optional: Release expensive resources (images, listeners)
// that don't need to persist if the app is killed.
}
override fun onDestroy() {
super.onDestroy()
// Rarely needed. Only for static cleanup or
// things the OS won't clean up automatically.
}
Professor's Mental Model:
onPause()is your "Final Chance" to save data.onStop()is for "Heavy Cleanup" (not critical data).onDestroy()is "Last Resort"—never trust it.
Handling Configuration Changes and Process Death (Advanced)
Think of an Activity lifecycle not just as a sequence of steps, but as a construction site. Sometimes, the building is just being renovated (Configuration Change). Sometimes, the building is completely demolished and rebuilt (Process Death). Your job is to ensure the "blueprints" (Data) survive even if the "scaffolding" (UI) is torn down.
Visualizing Data Survival
Different types of data survive different disasters. Use the buttons below to simulate events and see which "buckets" of data remain intact.
Small text, scroll position.
Lists, complex objects, logic.
User settings, drafts, files.
The Activity is destroyed and recreated. The process stays alive.
The entire app is wiped from memory. The Activity is recreated from scratch.
1 The "Sticky Note" Strategy: onSaveInstanceState
onSaveInstanceState() is like a sticky note you leave on the door before you leave a room. It's useful for small, temporary UI state (like the text in a text field or the scroll position).
The Limitation
This sticky note has a size limit (approx 1MB). If you try to save a large list of images or a complex object, your app will crash. Furthermore, the system might not call this if the user presses the Back button or if the process is killed abruptly.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// ❌ Risky: If user presses Back, this might not run.
// ❌ Limit: Cannot save large data here.
outState.putString("draft", editText.text.toString())
}
// ✅ Save immediately as user types
editText.doAfterTextChanged { text ->
// Save to persistent storage (DB, SharedPreferences)
preferenceManager.saveDraft(text.toString())
}
// Use Bundle only for tiny UI restoration
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("scrollPos", recyclerView.scrollPos)
}
2 The "Safe House": ViewModel
A ViewModel is a special container that survives configuration changes (like rotation). It lives in a separate memory store that the Activity doesn't own. When the Activity is destroyed and recreated, the ViewModel is passed the exact same instance.
The "Process Death" Trap
The ViewModel is NOT persistent. If the system kills your process (e.g., low memory), the ViewModel is wiped out. When the user returns, onCreate() runs with a fresh ViewModel. You must check if data is null and reload it.
class MyViewModel : ViewModel() {
// LiveData survives rotation
private val _items = MutableLiveData>()
val items: LiveData> = _items
fun loadItems() {
// This runs ONCE. If rotated, this is skipped because data exists.
viewModelScope.launch {
_items.value = repository.getItems()
}
}
}
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Check if data is missing (Process Death) before loading
if (viewModel.items.value == null) {
viewModel.loadItems()
}
}
}
3 Resource Management: When to Clean Up?
Because ViewModel survives rotation but Activity doesn't, you must be careful about where you register listeners (like sensors or network callbacks). If you register them in the ViewModel, they might keep running even when the screen is off or the Activity is in the background.
class MyViewModel : ViewModel() {
// ❌ Bad: Sensor keeps running even if Activity is destroyed
init {
sensorManager.registerListener(listener, ...)
}
}
class MyActivity : AppCompatActivity() {
// ✅ Good: Register in onStart, Unregister in onStop
override fun onStart() {
super.onStart()
sensorManager.registerListener(listener, ...)
}
override fun onStop() {
super.onStop()
sensorManager.unregisterListener(listener)
}
}
Professor's Mental Model:
onSaveInstanceStateis for "Sticky Notes" (tiny UI state).ViewModelis for "Furniture" (survives rotation, not demolition).Database/Filesis for "Foundation" (survives everything).
Android Development Basics: Best Practices for Lifecycle Management
Stop thinking about individual callbacks like onPause() or onResume() as isolated events. Instead, design your UI and resources around the current state of the Activity. Think of it like a smart room: you don't manually flip switches every time someone walks in; you set a rule: "If occupied, turn lights on."
Visualizing State-Based Automation
In modern Android, you tell the system what state you need (e.g., RESUMED), and it handles the start/stop automatically. Click the states below to see how the "Work" (like a network request or sensor) behaves.
Select the Activity State:
1 Design for State, Not Events
Beginners often try to handle every possible sequence by putting logic in multiple callbacks. This leads to tangled, error-prone code. The reality: the state (what the Activity can currently do) is more important than the event (which method was just called).
The Rule of Thumb
If your camera should only run when the Activity is RESUMED, start it in onResume() and stop it in onPause(). Don't try to start it in onStart()—you'll introduce bugs where the camera runs while the Activity is visible but not interactive (during a dialog).
// ❌ Tangled logic across multiple callbacks
override fun onStart() {
super.onStart()
// Risk: Runs even if dialog is blocking input
startCamera()
}
override fun onResume() {
super.onResume()
// Redundant: Camera already running
startCamera()
}
// ✅ Clean state-based logic
override fun onResume() {
super.onResume()
startCamera() // Only runs when interactive
}
override fun onPause() {
super.onPause()
stopCamera() // Pauses immediately
}
2 Pitfall: Over-using onCreate
onCreate() is for one-time initialization of the structure—inflating layout, creating ViewModels, setting up adapters. It is not for loading data that might change or be needed only while visible. Heavy work here blocks the first frame, causing jank.
The Right Place for Data
Load data based on state visibility. In onStart(), begin loading data that should be ready when the UI becomes visible. In onResume(), start work that requires user attention.
3 Use Lifecycle-Aware Components
Android provides components that automatically respond to state changes when you tie them to a LifecycleOwner (like an Activity). This removes the need for you to manually start/stop things in every callback.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ✅ RIGHT: Runs only while RESUMED, pauses automatically
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
// This block suspends when Activity leaves RESUMED state
val data = repository.loadHeavyData()
updateUi(data) // Safe: only called when UI is interactive
}
}
}
Professor's Mental Model:
ViewModelkeeps data across configuration changes (rotation).lifecycleScopecancels work automatically when the Activity is destroyed.repeatOnLifecycleruns code only while in a specific state.