how to manage Activity lifecycle in Android

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.

Foundation (onCreate)
Furniture
You
Professor's Note: The room is empty. Waiting for lifecycle events...

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.

❌ WRONG
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    
    // ❌ Blocks UI thread
    val data = fetchDataFromNetwork() 
    textView.text = data
}
✅ RIGHT
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.

Example: Sensor Manager
// 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:

  • onCreate builds the stage.
  • onStart turns up the lights.
  • onResume cues 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.

RESUMED
STARTED
CREATED
Professor's Note: The stack is empty. Start building from the bottom up.

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.

Kotlin: Lifecycle-Aware Coroutines Best Practice
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:

  • onResume is your "Go" signal.
  • onPause is 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.

Created
Started
Resumed
Stopped
Destroyed
RESUMED
PAUSED

System Dialog

Professor's Note: Ready. The app is fully interactive.

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
Kotlin: The Correct Pattern Best Practice
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.

RESUMED (Interactive)
STARTED (Visible)
CREATED (Initialized)
Status: Active

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.

Professor's Note: All zones are active. You are fully interactive.

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.

❌ WRONG
override fun onStop() {
    super.onStop()
    // RISKY: If system kills after onPause(),
    // this never runs. Data is lost.
    saveUserProgressToPrefs()
}
✅ RIGHT
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.

Kotlin: The Safe Pattern
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.

UI State (Bundle) Survives

Small text, scroll position.

ViewModel Data Survives

Lists, complex objects, logic.

Persistent Data (DB) Survives

User settings, drafts, files.

PROCESS KILLED

The Activity is destroyed and recreated. The process stays alive.

The entire app is wiped from memory. The Activity is recreated from scratch.

Professor's Note: All data buckets are currently safe. Waiting for an event...

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.

❌ WRONG
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())
}
✅ RIGHT
// ✅ 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.

Kotlin: ViewModel Pattern Best Practice
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.

❌ WRONG
class MyViewModel : ViewModel() {
    // ❌ Bad: Sensor keeps running even if Activity is destroyed
    init {
        sensorManager.registerListener(listener, ...)
    }
}
✅ RIGHT
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:

  • onSaveInstanceState is for "Sticky Notes" (tiny UI state).
  • ViewModel is for "Furniture" (survives rotation, not demolition).
  • Database/Files is 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.

Work Paused
State: UNKNOWN

Select the Activity State:

Professor's Note: Waiting for state selection...

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).

❌ WRONG
// ❌ 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() 
}
✅ RIGHT
// ✅ 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.

Kotlin: Lifecycle-Aware Coroutines Best Practice
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:

  • ViewModel keeps data across configuration changes (rotation).
  • lifecycleScope cancels work automatically when the Activity is destroyed.
  • repeatOnLifecycle runs code only while in a specific state.

Post a Comment

Previous Post Next Post