Introduction
The Android lifecycle is a fundamental concept that determines how Android components (particularly Activities and Fragments) behave from creation to destruction. Mastering these lifecycles is crucial for developing stable, efficient, and responsive Android applications. This comprehensive cheatsheet provides a detailed understanding of lifecycle stages, their practical implications, and best practices for managing them effectively. Whether you’re building simple apps or complex applications, proper lifecycle management prevents memory leaks, crashes, and ensures smooth user experiences.
Core Lifecycle Concepts
Lifecycle Components
Android Jetpack’s Lifecycle library provides a structured way to handle lifecycle events:
- LifecycleOwner: Interface for components with lifecycles (Activities, Fragments)
- LifecycleObserver: Interface for components that need to observe lifecycle changes
- Lifecycle.Event: Enumeration of lifecycle events (ON_CREATE, ON_START, etc.)
- Lifecycle.State: Enumeration of lifecycle states (CREATED, STARTED, etc.)
Lifecycle-Aware Components
Components that automatically adjust their behavior based on the lifecycle state of their hosts:
- ViewModel: Survives configuration changes, cleared when LifecycleOwner is destroyed
- LiveData: Only updates observers in active lifecycle states
- LifecycleService: Service that implements LifecycleOwner
- ProcessLifecycleOwner: Provides lifecycle for the whole application process
Activity Lifecycle
Lifecycle Stages Overview
Lifecycle Method | Description | Common Use Cases |
---|
onCreate() | Called when activity is first created | Initialize essential components, set content view, bind views |
onStart() | Called when activity becomes visible | Register BroadcastReceivers, initialize UI-related resources |
onResume() | Called when activity starts interacting with user | Start animations, acquire exclusive resources (camera, sensors) |
onPause() | Called when activity loses focus but is still visible | Pause animations, release non-critical resources, save draft data |
onStop() | Called when activity is no longer visible | Release UI-related resources, unregister BroadcastReceivers |
onDestroy() | Called before activity is destroyed | Clean up resources, unregister listeners, prevent memory leaks |
onRestart() | Called after onStop() before activity restarts | Re-initialize components that were released in onStop() |
Visual Representation of Activity Lifecycle
┌─────────────────┐
│ onCreate() │◄──────────────────┐
└────────┬────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ onStart() │◄─────┐ │
└────────┬────────┘ │ │
│ │ │
▼ │ │
┌─────────────────┐ │ │
│ onResume() │ │ │
└────────┬────────┘ │ │
│ │ │
Activity running │ │
│ │ │
▼ │ │
┌─────────────────┐ │ │
│ onPause() │ │ │
└────────┬────────┘ │ │
│ │ │
▼ │ │
┌─────────────────┐ │ │
│ onStop() ├──────┘ │
└────────┬────────┘ │
│ │
▼ │
┌─────────────────┐ ┌────────────┴───────┐
│ onDestroy() │ │ onRestart() │
└─────────────────┘ └────────────────────┘
Lifecycle Code Example
class MyActivity : AppCompatActivity() {
private val TAG = "MyActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate: Activity is created")
// Initialize ViewModels, views, and other components
// Restore saved instance state if needed
savedInstanceState?.let {
val savedText = it.getString("KEY_TEXT")
// Use restored data
}
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart: Activity is visible but may not have focus")
// Register BroadcastReceivers
LocalBroadcastManager.getInstance(this).registerReceiver(
myReceiver,
IntentFilter("MY_ACTION")
)
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume: Activity has focus and user can interact")
// Start camera, sensors, or animations
startLocationUpdates()
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause: Activity is partially visible but loses focus")
// Pause ongoing operations, save draft data
pauseAnimations()
saveDraftData()
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop: Activity is no longer visible")
// Unregister receivers, release resources
LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver)
stopLocationUpdates()
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy: Activity is being destroyed")
// Final cleanup, prevent memory leaks
clearReferences()
}
override fun onRestart() {
super.onRestart()
Log.d(TAG, "onRestart: Activity is restarting after being stopped")
// Prepare to start again
prepareForRestart()
}
override fun onSaveInstanceState(outState: Bundle) {
// Save UI state
outState.putString("KEY_TEXT", textView.text.toString())
super.onSaveInstanceState(outState)
}
}
Activity State Transitions
Transition | Methods Called | Triggered By |
---|
Activity launched | onCreate() → onStart() → onResume() | Starting activity for first time |
User navigates away | onPause() → onStop() | User presses home, opens another app |
User returns to activity | onRestart() → onStart() → onResume() | User returns to app from recents |
Configuration change | onPause() → onStop() → onDestroy() → onCreate() → onStart() → onResume() | Screen rotation, language change |
Activity finish | onPause() → onStop() → onDestroy() | User press back, activity calls finish() |
System kills process | (no lifecycle methods called) | System needs resources |
Fragment Lifecycle
Fragment Lifecycle Stages
Lifecycle Method | Description | Common Use Cases |
---|
onAttach() | Called when fragment is attached to activity | Store activity context, set up communication with activity |
onCreate() | Fragment is being created | Initialize non-UI resources, restore saved state |
onCreateView() | System calls to create view hierarchy | Inflate layout, initialize view references |
onViewCreated() | Called after onCreateView() | Setup view properties, attach listeners |
onStart() | Fragment becomes visible | Initialize UI-related components |
onResume() | Fragment is visible and has focus | Activate UI components, start animations |
onPause() | Fragment visible but loses focus | Pause animations, save UI state |
onStop() | Fragment no longer visible | Release UI resources |
onDestroyView() | View hierarchy being removed | Clean up view resources, detach listeners |
onDestroy() | Fragment being destroyed | Final cleanup of fragment resources |
onDetach() | Fragment detached from activity | Clean up activity references |
Visual Representation of Fragment Lifecycle
┌─────────────────┐
│ onAttach() │
└────────┬────────┘
│
▼
┌─────────────────┐
│ onCreate() │
└────────┬────────┘
│
▼
┌─────────────────┐
│ onCreateView() │
└────────┬────────┘
│
▼
┌─────────────────┐
│ onViewCreated() │
└────────┬────────┘
│
▼
┌─────────────────┐
│ onStart() │
└────────┬────────┘
│
▼
┌─────────────────┐
│ onResume() │
└────────┬────────┘
│
Fragment active
│
▼
┌─────────────────┐
│ onPause() │
└────────┬────────┘
│
▼
┌─────────────────┐
│ onStop() │
└────────┬────────┘
│
▼
┌─────────────────┐
│ onDestroyView() │
└────────┬────────┘
│
▼
┌─────────────────┐
│ onDestroy() │
└────────┬────────┘
│
▼
┌─────────────────┐
│ onDetach() │
└─────────────────┘
Fragment Lifecycle Code Example
class MyFragment : Fragment() {
private val TAG = "MyFragment"
private var _binding: FragmentMainBinding? = null
private val binding get() = _binding!!
override fun onAttach(context: Context) {
super.onAttach(context)
Log.d(TAG, "onAttach: Fragment attached to context")
// Get reference to hosting activity if needed
if (context is MyListener) {
listener = context
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate: Fragment is created")
// Get arguments, initialize non-UI related components
arguments?.let {
val param1 = it.getString("ARG_PARAM1")
// Process arguments
}
// Retain fragment instance across configuration changes if needed
// retainInstance = true
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
Log.d(TAG, "onCreateView: Creating view hierarchy")
// Inflate layout
_binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(TAG, "onViewCreated: View hierarchy created")
// Setup views, attach listeners
binding.button.setOnClickListener {
// Handle click
}
// Observe LiveData
viewModel.data.observe(viewLifecycleOwner) { data ->
binding.textView.text = data
}
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart: Fragment visible")
// Initialize UI components
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume: Fragment active and has focus")
// Start animations, acquire resources
}
override fun onPause() {
super.onPause()
Log.d(TAG, "onPause: Fragment loses focus")
// Pause ongoing operations
}
override fun onStop() {
super.onStop()
Log.d(TAG, "onStop: Fragment no longer visible")
// Release UI resources
}
override fun onDestroyView() {
super.onDestroyView()
Log.d(TAG, "onDestroyView: View hierarchy destroyed")
// Clean up view resources
_binding = null // Prevent memory leaks
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy: Fragment destroyed")
// Final cleanup
}
override fun onDetach() {
super.onDetach()
Log.d(TAG, "onDetach: Fragment detached from activity")
// Clear activity references
listener = null
}
companion object {
fun newInstance(param1: String) =
MyFragment().apply {
arguments = Bundle().apply {
putString("ARG_PARAM1", param1)
}
}
}
}
Activity vs Fragment Lifecycle
Event | Activity Methods | Fragment Methods |
---|
Creation | onCreate() | onAttach() → onCreate() → onCreateView() → onViewCreated() → onActivityCreated() |
Becoming Visible | onStart() | onStart() |
Gaining Focus | onResume() | onResume() |
Losing Focus | onPause() | onPause() |
No Longer Visible | onStop() | onStop() |
Destruction | onDestroy() | onDestroyView() → onDestroy() → onDetach() |
Managing Configuration Changes
Lifecycle During Configuration Changes
When a configuration change occurs (like screen rotation):
- Activity: onPause() → onStop() → onDestroy() → onCreate() → onStart() → onResume()
- Fragment: onPause() → onStop() → onDestroyView() → onDestroy() → onCreate() → onCreateView() → onViewCreated() → onStart() → onResume()
Handling Configuration Changes
Approach | Implementation | Best For |
---|
ViewModel | Use ViewModels to retain UI-related data | Most UI data persistence needs |
onSaveInstanceState() | Override to save lightweight data | Small amounts of UI state |
Retained Fragment | Set retainInstance = true | Complex non-UI data (deprecated) |
Setting in Manifest | android:configChanges=”orientation|screenSize” | Special cases (use sparingly) |
ViewModel Use Example
class MainViewModel : ViewModel() {
// Data will survive configuration changes
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
init {
// Will only execute once, not on every configuration change
fetchUsers()
}
private fun fetchUsers() {
viewModelScope.launch {
// Fetch data and update LiveData
}
}
// Called when ViewModel is finally destroyed (not on config changes)
override fun onCleared() {
super.onCleared()
// Clean up resources
}
}
// In Activity/Fragment
private val viewModel: MainViewModel by viewModels()
onSaveInstanceState Example
// Activity
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("KEY_TEXT", binding.editText.text.toString())
outState.putInt("KEY_POSITION", listPosition)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Restore state
savedInstanceState?.let {
binding.editText.setText(it.getString("KEY_TEXT"))
listPosition = it.getInt("KEY_POSITION")
}
}
Lifecycle-Aware Components
Implementing LifecycleObserver
class MyLocationObserver(
private val context: Context,
private val callback: (Location) -> Unit
) : DefaultLifecycleObserver {
private var locationManager: LocationManager? = null
override fun onStart(owner: LifecycleOwner) {
// When lifecycle owner starts
if (hasLocationPermission()) {
locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager?.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L,
10f,
locationListener
)
}
}
override fun onStop(owner: LifecycleOwner) {
// When lifecycle owner stops
locationManager?.removeUpdates(locationListener)
}
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
callback(location)
}
// Other required methods
}
}
// In Activity or Fragment
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Create observer and add it to lifecycle
val locationObserver = MyLocationObserver(this) { location ->
// Handle location update
}
// Register observer
lifecycle.addObserver(locationObserver)
}
}
ProcessLifecycleOwner
Provides lifecycle for the whole application process:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Add application-level lifecycle observer
ProcessLifecycleOwner.get().lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
// App moved to foreground
Log.d("App", "Application moved to foreground")
}
override fun onStop(owner: LifecycleOwner) {
// App moved to background
Log.d("App", "Application moved to background")
}
}
)
}
}
Common Lifecycle Challenges and Solutions
Memory Leaks
Challenge | Solution |
---|
Holding Activity reference in background thread | Use WeakReference or implement proper cancellation |
Inner classes holding implicit Activity reference | Use static inner classes with WeakReference |
Not clearing View references | Set view references to null in onDestroyView() |
Registering listeners without unregistering | Always unregister in appropriate lifecycle method |
Coroutine scope not cancelled | Use viewModelScope or lifecycleScope for automatic cancellation |
Lifecycle Mismatch
Problem | Solution |
---|
Updates after activity/fragment destroyed | Use lifecycle-aware components (LiveData) |
Async callbacks after onDestroy() | Check isAdded() for fragments, use lifecycle state checks |
Animation crash after view destroyed | Cancel animations in onPause() or onStop() |
Camera/sensors used after lifecycle end | Release resources in appropriate lifecycle method |
Handling Lifecycle Events in Adapters
class MyAdapter(
private val fragment: Fragment,
private val items: List<Item>
) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
init {
// Register lifecycle observer to handle adapter cleanup
fragment.viewLifecycleOwner.lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
// Clean up resources when view is destroyed
cancelAllImageLoading()
}
}
)
}
// Rest of adapter implementation
}
Best Practices for Lifecycle Management
General Best Practices
- Use Lifecycle Libraries: Leverage ViewModel, LiveData, and lifecycle-aware components
- Proper Resource Management: Acquire resources in onStart/onResume, release in onPause/onStop
- Avoid Long Operations in Lifecycle Methods: Keep onCreate() and other methods lightweight
- Use CoroutineScope Extensions: Use lifecycleScope and viewModelScope for structured concurrency
- Avoid UI Updates in Background: Only update UI when lifecycle is in appropriate state
Activity-Specific Best Practices
- Delegate to Fragments: Use activities as containers and navigation controllers
- Don’t Retain Activity References: Avoid static references to activities
- Handle Back Navigation Properly: Implement proper back stack behavior
- Use Single Activity Architecture: Consider using a single activity with multiple fragments
Fragment-Specific Best Practices
- ViewBinding Cleanup: Set binding to null in onDestroyView()
- Check isAdded(): Before performing operations that require activity context
- Use viewLifecycleOwner: For LiveData observers (not ‘this’)
- Use FragmentManager Transactions Carefully: Be aware of fragment lifecycle during transactions
Testing Lifecycle Components
// Testing Activity Lifecycle using ActivityScenarioRule
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun activityRecreation_maintainsState() {
// Set state
activityRule.scenario.onActivity { activity ->
activity.findViewById<EditText>(R.id.editText).setText("Test")
}
// Recreate activity
activityRule.scenario.recreate()
// Check if state preserved
activityRule.scenario.onActivity { activity ->
assertEquals("Test", activity.findViewById<EditText>(R.id.editText).text.toString())
}
}
}
// Testing ViewModel with TestCoroutineDispatcher
@ExperimentalCoroutinesApi
class MyViewModelTest {
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
private val testDispatcher = StandardTestDispatcher()
@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun `fetchData updates LiveData on success`() = runTest {
// Create ViewModel
val viewModel = MyViewModel()
// Fetch data
viewModel.fetchData()
// Advance time to complete coroutines
advanceUntilIdle()
// Assert LiveData updated
assertNotNull(viewModel.data.value)
}
}
Resources for Further Learning
Official Documentation
Codelab Tutorials
Advanced Resources