Android Activities and Activity Lifecycle
Introduction
In the realm of Android app development, an Activity represents a single screen with a user interface. Activities are one component among several (others include Services, Broadcast Receivers, and Content Providers) that make up an Android application. Users can interact with the UI components defined in activities to perform actions and move from one activity to another within the app. Managing these activities involves understanding their lifecycle, which dictates how they are created, started, resumed, paused, stopped, and destroyed. This article aims to provide detailed insights into Android activities and their lifecycle.
What is an Activity?
An activity is designed to be an entry point for user interaction, acting as the primary means by which users exchange data with your app. It is essentially a window or screen displayed on the device by the app and can range from complex multi-screen applications to simple dialog boxes. Each activity has its own user interface and behavior defined in a class that extends Activity
or AppCompatActivity
.
Creating an Activity
To create a new activity in an Android project, you should define a new class that extends Activity
or AppCompatActivity
(recommended for newer projects due to support for backward compatibility through the AppCompat library). Here is a simple example:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
In this example, MainActivity
extends AppCompatActivity
. The onCreate()
method is overridden to inflate the user interface defined in activity_main.xml
.
The Activity Lifecycle
The lifecycle of an activity consists of three main states and four associated callbacks that handle transitions between these states. These states and callbacks manage how the activity interacts with the user and other system components.
Three Main States:
- Resumed: The activity is at the top of the stack and interacts with the user. It is the equivalent of being fully visible on the screen.
- Paused: The activity is not visible because another semi-transparent or smaller activity has been launched on top of it. It is still alive and the user can switch back to it instantly.
- Stopped: The activity is no longer visible to the user but remains in memory and retains its state. It’s not engaged in any user interaction.
Four Associated State Transitions:
- Active: The activity is visible to the user and active.
- Inactive: The activity is not visible and is considered inactive.
- Background: The activity is not visible and is not attached to a window.
- Inactive and Background: The activity is both inactive and located in the background.
Lifecycle Callback Methods:
- onCreate(): Called once when the activity is first created. Used to initialize essential components like layout, views, and variables.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }
- onStart(): Called after
onCreate()
or if the activity comes to the foreground from a stopped state. Activity becomes visible but not yet interactive.@Override protected void onStart() { super.onStart(); // Perform actions when the activity starts }
- onResume(): Called immediately after
onStart()
or when the activity returns to the foreground from a paused state. Marks the start of user interaction with the activity.@Override protected void onResume() { super.onResume(); // Perform actions when the activity resumes }
- onPause(): Called when the activity loses focus but is still partially visible (e.g., covered by a half-transparent dialog). It is also called when the activity is about to go to the background. This callback method typically stops animations and saves CPU resources.
@Override protected void onPause() { super.onPause(); // Perform actions when the activity pauses }
- onStop(): Called when the activity is not visible and goes to the background. This state follows
onPause()
when an activity is moved to the background or before the activity is destroyed.@Override protected void onStop() { super.onStop(); // Perform actions when the activity stops }
- onRestart(): Called just before
onStart()
when an activity moves from a stopped state back to the running state.@Override protected void onRestart() { super.onRestart(); // Perform actions when the activity restarts }
- onDestroy(): Called before the activity is destroyed and removed from the stack. Typically, this method is used for cleaning up resources like closing database connections, stopping threads, etc.
@Override protected void onDestroy() { super.onDestroy(); // Perform actions when the activity destroys }
- onCreate(): Called once when the activity is first created. Used to initialize essential components like layout, views, and variables.
State Diagram of Activity Lifecycle
The diagram above illustrates the various transition paths between different parts of the activity lifecycle.
Saving and Restoring State
When the system destroys an activity to save memory or for other reasons, developers need to ensure that essential data is preserved. This can be done by overriding the onSaveInstanceState()
method.
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("score", score); // Save score variable
outState.putString("time", currentTime);// Save time string
}
The outState
bundle is passed to onRestoreInstanceState()
when the activity is recreated:
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
score = savedInstanceState.getInt("score"); // Restore score variable
currentTime = savedInstanceState.getString("time"); // Restore time string
}
Alternatively, values can also be restored in onCreate()
:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
score = savedInstanceState.getInt("score")
currentTime = savedInstanceState.getString("time");
}
}
Managing Configuration Changes
Changes in configuration settings such as screen orientation, keyboard availability, system language, etc., can cause the destruction and recreation of an activity. It is crucial to understand how to handle these changes to prevent loss of data and preserve the user experience.
By default, the activity is destroyed and recreated upon a configuration change. However, handling orientation changes can be more optimized using the android:configChanges
attribute in the manifest file. For example:
<activity android:name=".GameActivity"
android:configChanges="orientation|screenSize">
</activity>
With android:configChanges
set, the activity does not get destroyed and recreated but instead calls onConfigurationChanged()
when the configuration changes.
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
}
}
Important Considerations
- Activity Stack: Android manages a stack of activities that form a path through the app. When a new activity is started, it is pushed onto the stack. When the current activity is finished through back navigation, it is popped off the stack.
- Saving Instance State vs. Persisting Data:
onSaveInstanceState()
is used for volatile data restoration across configuration changes. For data persistence across app relaunches, consider using other mechanisms like Shared Preferences, SQLite databases, or cloud storage. - Back Navigation: The system provides back navigation functionality. Pressing the back button typically finishes the current activity and returns to the previous one in the stack.
- Foreground and Background States: Activities in the foreground state are interactive and have user attention. Activities in the background state are not interactive and are less likely to be interrupted.
- Screen Visibility and Interactivity: While an activity is in the Resumed state, the user interacts directly with it, and it is visible. In the Paused state, the activity might be visible but is less likely to receive immediate user attention.
- System Interventions: The system might destroy activities when under memory pressure. It’s crucial to handle lifecycle events gracefully and not rely on
onDestroy()
for saving essential data.
Conclusion
Understanding the lifecycle of Android activities is fundamental for building robust and responsive applications. Developers must take care to manage state transitions effectively, preserving essential data and optimizing resources. Familiarity with the correct use of lifecycle methods and configurations enables developers to craft smooth user experiences, even as they navigate through various screens and configurations within an app. Whether it’s handling orientation changes, saving instance state, or managing transitions between activities, careful consideration of the lifecycle ensures a seamless and efficient application.
Examples, Set Route and Run the Application Then Data Flow: A Step-by-Step Guide to Android Activities and Activity Lifecycle for Beginners
Understanding how Activities work in Android is fundamental to developing an app that responds well to user interactions and handles transitions smoothly between different screens. This beginner-friendly guide will walk you through creating a simple Android application to demonstrate the use of Activities and their lifecycle.
What is an Activity?
An Activity in Android is a single, focused thing that a user can do. For example, it can be a screen with buttons and text, or a screen displaying video content. An Activity often corresponds to a single screen in the user interface (UI), though some Activities may not have a UI at all.
Setting Up Your Project
- Install Android Studio: If you haven’t already, download and install Android Studio, the official IDE for Android development.
- Create a New Project: Open Android Studio and create a new project.
- Choose “Empty Activity” as the template.
- Name your application something simple, like
LifecycleDemo
. - Set the package name to anything you prefer; let's use
com.example.lifecycle
. - Choose a Java programming language.
- Click on “Finish” to create your project.
- Explore the Generated Files:
MainActivity.java
: This is the entry point into the app.activity_main.xml
: Corresponds to the layout resource for MainActivity.
Designing the User Interface
Open activity_main.xml
and add simple UI components. Replace the existing code with:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Go to Second Activity"
android:layout_centerInParent="true" />
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="#FF0000"
android:layout_below="@id/btn_next"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp" />
</RelativeLayout>
Creating a New Activity
- Right-click
app > java > com.example.lifecycle
in your project explorer. - Select
New > Activity > Empty Activity
and name itSecondActivity
.
Update the Layout for SecondActivity (Optional)
For SecondActivity
, we'll create a similar layout but with a back button and its own status TextView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Back to First Activity"
android:layout_centerInParent="true" />
<TextView
android:id="@+id/tv_second_activity_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="#FF0000"
android:layout_below="@id/btn_back"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp" />
</RelativeLayout>
Implementing Button Navigation Between Activities
FirstActivity Implementation
Inside MainActivity.java
, modify it to handle navigation to SecondActivity
:
package com.example.lifecycle;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView tvStatus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvStatus = findViewById(R.id.tv_status);
Button btnNext = findViewById(R.id.btn_next);
btnNext.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
updateStatus("onCreate called");
}
@Override
protected void onStart() {
super.onStart();
updateStatus("onStart called");
}
@Override
protected void onResume() {
super.onResume();
updateStatus("onResume called");
}
@Override
protected void onPause() {
super.onPause();
updateStatus("onPause called");
}
@Override
protected void onStop() {
super.onStop();
updateStatus("onStop called");
}
@Override
protected void onDestroy() {
super.onDestroy();
updateStatus("onDestroy called");
}
private void updateStatus(String message){
String currentText = tvStatus.getText().toString();
tvStatus.setText(currentText + "\n" + message);
}
}
SecondActivity Implementation
Similarly, update SecondActivity.java
to handle going back:
package com.example.lifecycle;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Button btnBack = findViewById(R.id.btn_back);
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SecondActivity.this, MainActivity.class);
startActivity(intent);
}
});
TextView tvSecondActivityStatus = findViewById(R.id.tv_second_activity_status);
tvSecondActivityStatus.setText("onCreate called");
}
@Override
protected void onStart() {
super.onStart();
TextView tvSecondActivityStatus = findViewById(R.id.tv_second_activity_status);
tvSecondActivityStatus.append("\nonStart called");
}
@Override
protected void onResume() {
super.onResume();
TextView tvSecondActivityStatus = findViewById(R.id.tv_second_activity_status);
tvSecondActivityStatus.append("\nonResume called");
}
@Override
protected void onPause() {
super.onPause();
TextView tvSecondActivityStatus = findViewById(R.id.tv_second_activity_status);
tvSecondActivityStatus.append("\nonPause called");
}
@Override
protected void onStop() {
super.onStop();
TextView tvSecondActivityStatus = findViewById(R.id.tv_second_activity_status);
tvSecondActivityStatus.append("\nonStop called");
}
@Override
protected void onDestroy() {
super.onDestroy();
TextView tvSecondActivityStatus = findViewById(R.id.tv_second_activity_status);
tvSecondActivityStatus.append("\nonDestroy called");
}
}
Running the Application
- Connect a Device or Start Emulator: Choose a device connected via USB or start an emulator from Android Studio.
- Run the App: Click the green “Run” triangle button in Android Studio.
- Select your run configuration and destination.
- Observe the Output: When running on an emulator or a device, you should:
- See the initial message on
MainActivity
showingonCreate
, thenonStart
, and finallyonResume
. - Click the "Go to Second Activity" button to see
pausing, stopping, destroying
, followed bycreating, starting, and resuming
inSecondActivity
. - Clicking "Back to First Activity" would show similar sequence of callbacks as before but would resume
MainActivity
and not recreate it.
- See the initial message on
Understanding Activity Lifecycle Callbacks
The lifecycle of an Android Activity is managed by the system. The system invokes specific methods (callbacks) at different times to manage the activity's state, such as:
- onCreate(): Called when the activity is initialized. Perfect for setting up your UI.
- onStart(): Follows
onCreate()
and whenever the activity comes into a foreground state (not visible but about to become visible). - onResume(): After
onStart()
is called when the activity comes into a user-visible state that the user interacts with. - onPause(): Triggered when another activity comes into partial visibility or when the activity is being stopped.
- onStop(): Called just before the activity becomes completely invisible to the user.
- onRestart(): Only after the activity is stopped then again resumed (comes into the front end).
- onDestroy(): Invoked when the activity is destroyed by the system or if it's finished.
Data Flow Between Activities
For passing small amounts of data between activities using Intent
, perform the following steps:
Sending Data From FirstActivity to SecondActivity
Modify the button click listener in MainActivity
:
btnNext.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("message", "Hello from First Activity!");
startActivity(intent);
}
});
Receiving Data in SecondActivity
Modify onCreate()
in SecondActivity
to receive the intent:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Button btnBack = findViewById(R.id.btn_back);
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SecondActivity.this, MainActivity.class);
startActivity(intent);
}
});
TextView tvSecondActivityStatus = findViewById(R.id.tv_second_activity_status);
String messageReceived = getIntent().getStringExtra("message");
tvSecondActivityStatus.setText(messageReceived != null ? messageReceived : "No message received!");
tvSecondActivityStatus.append("\nonCreate called");
}
Summary
In this guide, we created a simple Android application with two activities, MainActivity
and SecondActivity
. We demonstrated navigating between activities using button clicks with Intent
s and observed the lifecycle callbacks of each activity.
The Activity Lifecycle is crucial for handling resources efficiently, saving states, and ensuring smooth transitions between different screens. By understanding and managing these callbacks, you can make your apps more responsive and robust.
Feel free to experiment with adding more callbacks and handling complex scenarios, which will help deepen your understanding of Android activities. Happy coding!