Android Activity Lifecycle: The Complete Guide I Wish I Had
Understanding the Activity lifecycle is crucial for Android development. Here's everything I learned the hard way, from onCreate to onDestroy, with real-world examples and common pitfalls.
Android Activity Lifecycle: The Complete Guide I Wish I Had
I still remember my first Android interview. The interviewer asked me to explain the Activity lifecycle, and I confidently started with "onCreate, onStart, onResume..."
Then they asked: "What happens if the user rotates the screen while your Activity is running?"
I froze.
That moment taught me that knowing the lifecycle methods isn't enough. You need to understand when they're called, why they exist, and what you should do in each one.
Let me share everything I've learned about the Activity lifecycle over the years—the mistakes I made, the lessons I learned, and the best practices that'll help you ace your interviews and build better apps.
The Basics: What is an Activity Lifecycle?
Think of an Activity as a living thing. It's born, it lives, it might pause, and eventually it dies. The Android system manages this lifecycle, calling specific methods at each stage.
Here's the thing: you don't control when these methods are called. The system does. Your job is to respond appropriately.
The Lifecycle Methods: A Deep Dive
onCreate(): The Birth
This is where your Activity is created. It's called once, when the Activity is first instantiated.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize UI components
setupViews()
// Restore saved state if available
savedInstanceState?.let {
restoreState(it)
}
}
What to do here:
- Set your content view
- Initialize UI components
- Restore saved state (if the Activity was recreated)
- Set up ViewModels, observers, etc.
What NOT to do:
- Don't do heavy work here (use background threads)
- Don't assume the Activity is visible yet
- Don't forget to call
super.onCreate()
I learned this the hard way. I once tried to make a network call in onCreate() and wondered why my app was slow. The Activity wasn't even visible yet, but I was blocking the main thread.
onStart(): Becoming Visible
This is called when the Activity becomes visible to the user. But here's the catch—it's not yet interactive.
override fun onStart() {
super.onStart()
// Activity is visible but not in foreground
registerBroadcastReceiver()
startLocationUpdates()
}
When is this useful?
- Registering broadcast receivers
- Starting location updates
- Preparing resources that should be available when visible
Common mistake: Trying to interact with the user here. The Activity isn't in the foreground yet, so user interactions won't work.
onResume(): The Spotlight
This is where your Activity is fully interactive. The user can now see it, touch it, and interact with it.
override fun onResume() {
super.onResume()
// Activity is now in foreground and interactive
resumeAnimations()
startCameraPreview()
refreshData()
}
What to do:
- Resume animations
- Start camera preview
- Refresh data that might have changed
- Resume sensors or foreground-only features
Pro tip: This is called every time the Activity comes to the foreground, even if it was just paused briefly. So don't do expensive operations here unless necessary.
onPause(): The Interruption
This is called when the system is about to resume another Activity. Notice I said "about to"—it might not happen immediately.
override fun onPause() {
super.onPause()
// Save important data
saveUserInput()
pauseAnimations()
releaseCamera()
}
Critical: This method is called before the next Activity's onResume(), so keep it lightweight. Heavy operations here will make your app feel slow.
What to do:
- Save important data
- Pause animations
- Release resources that shouldn't run in background
- Unregister listeners that aren't needed
I once forgot to release the camera in onPause(), and users complained that other apps couldn't access the camera. Lesson learned.
onStop(): Out of Sight
Called when the Activity is no longer visible. The Activity might be destroyed, or it might come back later.
override fun onStop() {
super.onStop()
// Activity is no longer visible
stopLocationUpdates()
unregisterBroadcastReceiver()
saveToDatabase()
}
Key point: This is a good place to do heavier operations like saving to a database, because the Activity isn't visible anyway.
onRestart(): The Comeback
Called after onStop() when the Activity is being restarted. This is followed by onStart().
override fun onRestart() {
super.onRestart()
// Activity is being restarted
// Usually not much to do here
}
Honestly? I rarely override this method. Most of the time, onStart() is sufficient.
onDestroy(): The End
This is the final call. The Activity is being destroyed.
override fun onDestroy() {
super.onDestroy()
// Final cleanup
cleanupResources()
cancelPendingOperations()
}
Important: Don't rely on this being called. The system might kill your process without calling onDestroy() in low memory situations.
The Configuration Change Problem
Remember my interview story? Here's what happens when the user rotates the screen:
- The current Activity is destroyed (
onPause(),onStop(),onDestroy()) - A new Activity is created with the new configuration
onCreate()is called with aBundlecontaining saved state
The solution? Save your state in onSaveInstanceState():
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("userInput", editText.text.toString())
outState.putInt("currentPosition", recyclerView.scrollPosition)
}
Then restore it in onCreate():
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
savedInstanceState?.let {
val userInput = it.getString("userInput")
val position = it.getInt("currentPosition")
// Restore state
}
}
Better solution? Use ViewModel. It survives configuration changes automatically. But that's a story for another article.
Common Pitfalls and How to Avoid Them
1. Memory Leaks
Holding references to Activities in static variables or long-lived objects is a recipe for memory leaks.
// BAD
object MyManager {
var currentActivity: Activity? = null
}
// GOOD
object MyManager {
var currentActivity: WeakReference<Activity>? = null
}
2. Forgetting to Clean Up
Always unregister what you register:
override fun onStart() {
super.onStart()
registerReceiver(broadcastReceiver, intentFilter)
}
override fun onStop() {
super.onStop()
unregisterReceiver(broadcastReceiver) // Don't forget this!
}
3. Doing Heavy Work on Main Thread
The lifecycle methods run on the main thread. Don't block them:
// BAD
override fun onResume() {
super.onResume()
val data = fetchDataFromNetwork() // Blocks main thread!
}
// GOOD
override fun onResume() {
super.onResume()
viewModelScope.launch {
val data = fetchDataFromNetwork() // Background thread
updateUI(data)
}
}
Best Practices
- Keep lifecycle methods lightweight - They're called frequently and on the main thread
- Use ViewModel for data - It survives configuration changes
- Save state properly - Use
onSaveInstanceState()for small, simple data - Clean up resources - Always unregister receivers, release cameras, etc.
- Test configuration changes - Rotate your device, change language, etc.
The Lifecycle in Practice
Here's a real-world example from one of my apps:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
private val locationReceiver = LocationBroadcastReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this)[MainViewModel::class.java]
setupObservers()
if (savedInstanceState == null) {
// First time creation
loadInitialData()
}
}
override fun onStart() {
super.onStart()
registerReceiver(locationReceiver, locationFilter)
}
override fun onResume() {
super.onResume()
viewModel.refreshData()
resumeLocationUpdates()
}
override fun onPause() {
super.onPause()
pauseLocationUpdates()
}
override fun onStop() {
super.onStop()
unregisterReceiver(locationReceiver)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("scrollPosition", recyclerView.computeVerticalScrollOffset())
}
}
Key Takeaways
Understanding the Activity lifecycle isn't just about passing interviews—it's about building apps that work correctly, handle edge cases, and provide a smooth user experience.
The lifecycle is your friend. Once you understand it, you can build apps that handle configuration changes gracefully, manage resources efficiently, and provide a great user experience.
Remember:
- onCreate() - Initialize
- onStart() - Become visible
- onResume() - Become interactive
- onPause() - About to lose focus
- onStop() - No longer visible
- onDestroy() - Being destroyed
Each method has a purpose. Use them wisely.
What's Next?
Now that you understand the Activity lifecycle, you're ready to dive deeper into:
- ViewModel and LiveData (surviving configuration changes)
- Fragments and their lifecycle
- Process death and state restoration
- Architecture components
But that's for another article. For now, practice implementing the lifecycle methods in a real app. Rotate your device. Change the language. Put your app in the background. See what happens.
That's how you really learn.
Good luck with your Android journey! 🚀