An activity is a screen shown in response to a user's action. For example, when you touch an app icon and it launches a screen, this first screen is the first activity. Technically speaking, an activity implements a part of the application’s visual user interface.
Activity States
An activity can have four states and we will discuss them briefly here.
Active
Activity is in an active state when it has the focus and it is ready to receive input from the user. The input can be in any form, most often a tap of a link or a button.
Inactive
When a user interacts with an activity or a screen, it usually causes a new activity to start and the system transfers the focus, or intent, to the new activity leaving the previous one inactive. An inactive activity is still there but in an inactive state. An inactive activity can receive focus and become active again.
Destroyed
An activity is destroyed when a user presses the back button. This action destroys the active activity and passes the focus to the previous one. Once an activity is destroyed, it must be created again; there is no way to bring a destroyed activity to life.
Hidden
An activity can remain active even when it is not visible to the user. This activity state is useful in certain multitasking scenarios.
All activities must be represented by the element in the manifest file or they will never run.
Tasks
A task is a collection of activities. Imagine a series of steps a user takes to complete a specific goal within your app. This sequence of activities, working together towards a common objective, forms a task. Tasks provide structure and maintain context for the user's journey through your app.
Same Task, Shared Workflow: Activities belonging to the same task share a common thread. They are part of a unified workflow, and their order of execution is crucial. These activities are stacked together within a single task, adhering to a Last-In, First-Out (LIFO) principle. The last activity added (launched) becomes the topmost activity in the task. When the user navigates back using the back button, the topmost activity is removed from the stack, revealing the previous activity underneath.
Example: Consider a photo editing task. You might have activities for opening an image, applying filters, and saving the edited image. These activities would all belong to the same task, with the back button allowing users to navigate back through the editing process.
// Example Kotlin code for starting an activity within the same task
val intent = Intent(this, EditImageActivity::class.java)
startActivity(intent)
Different Tasks, Independent Functionalities: Not all activities are created equal. Some tasks might be entirely independent of others. For instance, an email app and a music player would likely function as separate tasks. Each task maintains its own independent stack of activities, and activities within different tasks cannot directly influence each other’s behavior.
Example: Imagine you’re checking your email while listening to music. These actions represent two distinct tasks. Pressing the back button within the email app wouldn’t take you back to the music player, and vice versa.
Back Stack
As users navigate through your app, launching new activities, a separate entity called the back stack keeps track of their journey. The back stack functions like a stack of plates, with each plate representing an activity. As new activities are launched, they’re added on top of the back stack. Conversely, pressing the back button removes the topmost activity (plate) from the back stack, revealing the previously viewed activity (plate) underneath.
Managing Tasks
Android’s default task and back stack management is suitable for most applications, but there are times when developers need more control over their app’s navigation flow. This article explores how to manipulate task behavior using manifest attributes and intent flags.
Default Task Behavior: By default, Android places all activities started in succession within the same task, forming a “last in, first out” stack. This intuitive management allows users to navigate backward through the activity stack seamlessly.
Interrupting Normal Behavior: There are scenarios where the default behavior might not align with the desired user experience. For instance, you might want an activity to start a new task or bring an existing instance of an activity to the foreground. Android provides several ways to achieve this:
- taskAffinity: Defines the relationship between different tasks and activities. Activities with the same affinity are generally grouped together in the same task.
- launchMode: Specifies how an activity should be launched within a task. It can be “standard,” “singleTop,” “singleTask,” or “singleInstance.”
- allowTaskReparenting: If set to true, allows an activity to move from its task to the task it has an affinity with when that task comes to the foreground.
- clearTaskOnLaunch: When set to true, every time the user leaves the task and returns to it, all activities except for the root activity are finished.
- alwaysRetainTaskState: If true, the task retains its state and is not reset when launched from the home screen.
- finishOnTaskLaunch: Similar to clearTaskOnLaunch, but it also finishes the root activity, effectively clearing the entire task upon launch.
- Using Intent Flags: In addition to manifest attributes, developers can use intent flags such as FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TOP to control how activities are launched and managed within a task.
Example Scenarios:
- Starting a new task for an activity that serves as a distinct workflow within your app.
- Bringing an existing instance of an activity to the foreground instead of creating a new one.
- Clearing all activities except for the root when leaving a task.
Defining Launch Modes
- Using the manifest file When you declare an activity in your manifest file, you can specify how the activity should associate with tasks when it starts.
- Using Intent flags When you call startActivity(), you can include a flag in the Intent that declares how (or whether) the new activity should associate with the current task.
Activity Launch Modes: Controlling Activity Creation
Now that we understand tasks and the back stack, let’s delve into launch modes. These modes dictate how activities are created and managed within the task hierarchy:
Types of Launch Modes
- standard
- singleTop
- singleTask
- singleInstance
Standard (default)
The default mode. The system creates a new instance of the activity in the task it was started from and routes the intent to it. The activity can be instantiated multiple times, each instance can belong to different tasks, and one task can have multiple instances.
The most common launch mode. Whenever an activity is launched with the standard mode (or no mode is explicitly specified), a new instance of the activity is created. This new instance is placed on top of the current task’s stack.
Example: Imagine you have activities A, B, C, and D in your app. Launching B from activity A using the standard mode would create a new instance of B and add it on top of the stack, resulting in A -> B. Launching B again from the existing instance of B would create another new instance, forming A -> B -> B.
- Launches a new instance of an activity in the task.
- Can create multiple instances of the same activity, assigned to the same or separate tasks.
- Launches a new instance of an activity in the task.
- Can create multiple instances of the same activity, assigned to the same or separate tasks.
Scenario:
A -> B -> C -> D
- Launching B again results in:
A -> B -> C -> D -> B
(B is created again)
// Example Kotlin code for standard launch mode
val intent = Intent(this, ActivityB::class.java)
startActivity(intent)
Single Top
If an instance of the activity already exists at the top of the current task, the system routes the intent to that instance through a call to its
onNewIntent()
method, rather than creating a new instance of the activity. The activity is instantiated multiple times, each instance can belong to different tasks, and one task can have multiple instances (but only if the activity at the top of the back stack is not an existing instance of the activity).
This mode offers more control over activity creation. If an activity is declared with launchMode=“singleTop”
in the manifest file, the following behavior occurs:
If an instance of the activity already exists at the top of the stack within the current task, a new instance won’t be created. Instead, the existing activity receives the intent that triggered the launch through its onNewIntent()
method. This method allows you to handle updates within the existing activity instance.
If the target activity with singleTop mode resides lower in the stack (not on top), a new instance is still created as usual.
Example: Let’s say activities A, B (with singleTop mode), C, and D are present. Launching D from C creates a new instance (A -> B -> C -> D). Now, launching B again from D would not create a new instance of B if it’s already at the top of the stack; instead, B’s onNewIntent() is called.
- Will not create a new activity if it’s already at the top of the stack.
Scenario:
A -> B -> C -> D
- Launching D again results in:
A -> B -> C -> D
(D is not created again)- Launching B results in:
A -> B -> C -> D -> B
(B is created again because it’s not at the top)
// Single Top launch mode example
val singleTopIntent = Intent(this, SingleTopActivity::class.java)
singleTopIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
startActivity(singleTopIntent)
// In SingleTopActivity:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// Handle the new intent here
}
<activity android:name=".ActivityB"
android:launchMode="singleTop">
</activity>
Single Top If an activity is already at the top of the stack, it will not be launched again. Instead, the onNewIntent() method will be called with the new intent.
Single Task
The system creates the activity at the root of a new task or locates the activity on an existing task with the same affinity. If an instance of the activity already exists, the system routes the intent to the existing instance through a call to its onNewIntent() method, rather than creating a new instance. Meanwhile all of the other activities on top of it are destroyed.
The singleTask launch mode ensures that only one instance of the activity exists in a task. If you launch an activity with singleTask mode, and it already exists in the current task (potentially with other activities on top of it in the stack), those previous activities will be destroyed, and the existing activity will receive the intent through its onNewIntent() method. This effectively creates a new single-activity task if the activity wasn't already running, or brings the existing task to the foreground if it was running in the background.
Example: Consider activities A, B (with singleTask mode), C, and D. Launching B from A creates a new task with B at the root (B). Launching C from B adds C on top of the stack (B -> C). Now, launching A again from C wouldn't create a new instance of A or add it to the current task. Instead, the existing task containing B would be brought to the foreground, destroying C on top, and B would likely receive the intent through onNewIntent() (B).
- Destroys any activities on top of the said activity if it’s already in the stack.
Scenario:
A -> B -> C
- Launching D results in:
A -> B -> C -> D
(D is created)- Launching B after D results in:
A -> B
(B is brought to top, others are destroyed)
// Single Task launch mode example
val singleTaskIntent = Intent(this, SingleTaskActivity::class.java)
singleTaskIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(singleTaskIntent)
// In SingleTaskActivity:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// Handle the new intent here
}
<activity android:name=".SingleTaskActivity" android:launchMode="singleTask" />
Single Task Creates a new task and instantiates the activity at the root of the new task. If an instance already exists, it brings that task to the foreground.
Single Instance
The behavior is the same as for "singleTask", except that the system doesn't launch any other activities into the task holding the instance. The activity is always the single and only member of its task. Any activities started by this one open in a separate task.
Similar to singleTask, but even more restrictive. It allows only one instance of the activity to exist in the entire system, not just within a single task. Any other activity started from an activity with singleInstance mode will be launched in a new task. This mode is typically used for very specific scenarios, like splash screens or login activities that should only have one instance running at a time.
Example: Suppose, activity D has "launchMode = singleInstance". Launching D creates a new task with D at the root (D). Launching any other activity (like A) would create a new task with that activity at the root (A), leaving D's task unaffected (separate tasks: D and A).
- Activity is launched in a new task, and this task can’t have any other activities.
Scenario:
- Task1:
A -> B -> C
- Launching D results in:
- Task1:
A -> B -> C
- Task2: D (D is created in a separate task)
- Launching D again results in:
- Task1:
A -> B -> C
- Task2:
D
(D is not created again, retrieved in its old state)- Creating a new activity E (not on singleInstance mode) results in:
- Task1:
A -> B -> C -> E
(E is created on task1)- Task2:
D
// Single Instance launch mode example
val singleInstanceIntent = Intent(this, SingleInstanceActivity::class.java)
singleInstanceIntent.flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
startActivity(singleInstanceIntent)
<activity android:name=".SingleInstanceActivity" android:launchMode="singleInstance" />
singleInstancePerTask
The activity can only run as the root activity of the task, the first activity that created the task, and therefore there can only be one instance of this activity in a task. In contrast to the singleTask launch mode, this activity can be started in multiple instances in different tasks if the FLAG_ACTIVITY_MULTIPLE_TASK or FLAG_ACTIVITY_NEW_DOCUMENT flag is set.
Note: "singleTask" and "singleInstancePerTask" remove all activities that are above the starting activity from the task. For example, suppose a task consists of root activity A with activities B and C. The task is A-B-C, with C on top. An intent arrives for an activity of type A. If A's launch mode is "singleTask" or "singleInstancePerTask", the existing instance of A receives the intent through onNewIntent(). B and C are finished, and the task is now A.
Choosing the Right Launch Mode
The appropriate launch mode depends on the specific functionality you aim to achieve within your app.
- Use standard mode for activities that don't require special handling of multiple instances or updates within a single instance.
- Use singleTop mode for activities that act as containers or entry points for tasks, where you only want one active instance at a time, and want to handle updates within that instance using onNewIntent().
- Use singleTask mode for activities that represent the core functionality of a distinct task, ensuring only one instance exists per task and potentially clearing the task stack to get there.
- Use singleInstance mode sparingly, for special cases where you truly need only one instance of an activity in the entire system (e.g., login or splash screen).
Android Activity Launch Modes Through Practical Implementation
MainActivity Implementation: MainActivity acts as our control panel, with buttons to initiate activities with each launch mode. Here’s how to set it up:
// MainActivity.kt
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val buttonStandard = findViewById<Button>(R.id.button_standard_launch)
buttonStandard.setOnClickListener {
Toast.makeText(this, "Standard Launch", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, ActivityA::class.java))
}
val buttonSingleTop = findViewById<Button>(R.id.button_single_top_launch)
buttonSingleTop.setOnClickListener {
Toast.makeText(this, "SingleTop Launch", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, ActivityA::class.java))
}
val buttonSingleTask = findViewById<Button>(R.id.button_single_task_launch)
buttonSingleTask.setOnClickListener {
Toast.makeText(this, "SingleTask Launch", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, ActivityB::class.java))
}
val buttonSingleInstance = findViewById<Button>(R.id.button_single_instance_launch)
buttonSingleInstance.setOnClickListener {
Toast.makeText(this, "SingleInstance Launch", Toast.LENGTH_SHORT).show()
startActivity(Intent(this, ActivityC::class.java))
}
val buttonNewTaskClearTop = findViewById<Button>(R.id.button_new_task_clear_top_launch)
buttonNewTaskClearTop.setOnClickListener {
Toast.makeText(this, "New Task and Clear Top", Toast.LENGTH_SHORT).show()
val intent = Intent(this, ActivityD::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(intent)
}
}
}
In this code, we define buttons that, when clicked, start activities with different launch modes using intents. We also display toast messages to indicate which mode is being activated.
XML Layout for Buttons: The user interface for MainActivity is defined in activity_main.xml. It includes buttons that correspond to each launch mode:
<!-- activity_main.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<!-- Standard Launch: Adds ActivityA on top of MainActivity -->
<Button
android:id="@+id/button_standard_launch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Standard Launch"
android:layout_marginBottom="8dp" />
<!-- SingleTop Launch: Adds ActivityA on top if not already there or brings it to top if it's already there -->
<Button
android:id="@+id/button_single_top_launch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SingleTop Launch"
android:layout_marginBottom="8dp" />
<!-- SingleTask Launch: Brings ActivityB to the top of the stack, clearing above it -->
<Button
android:id="@+id/button_single_task_launch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SingleTask Launch"
android:layout_marginBottom="8dp" />
<!-- SingleInstance Launch: Opens ActivityC in a new task with its own backstack -->
<Button
android:id="@+id/button_single_instance_launch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SingleInstance Launch"
android:layout_marginBottom="8dp" />
<!-- New Task and Clear Top: Starts ActivityD in a new task and clears any existing instances of it -->
<Button
android:id="@+id/button_new_task_clear_top_launch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="New Task and Clear Top"
android:layout_marginBottom="8dp" />
</LinearLayout>
Other layout files:
<!-- activity_b.xml -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_start_activity_c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Activity C"
android:layout_centerInParent="true" />
</RelativeLayout>
<!-- activity_c.xml -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_start_activity_d"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Activity D"
android:layout_centerInParent="true" />
</RelativeLayout>
<!-- activity_d.xml -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_start_activity_e"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start Activity E"
android:layout_centerInParent="true" />
</RelativeLayout>
<!-- activity_e.xml -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- No button needed for Activity E as it's the last one in the sequence -->
<TextView
android:id="@+id/text_view_activity_e"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Activity E"
android:layout_centerInParent="true" />
</RelativeLayout>
Manifest for Example:
<activity android:name=".MainActivity">
<!-- MainActivity has standard launch mode by default -->
</activity>
<activity android:name=".ActivityA"
android:launchMode="singleTop">
<!-- ActivityA has singleTop launch mode -->
</activity>
<activity android:name=".ActivityB"
android:launchMode="singleTask">
<!-- ActivityB has singleTask launch mode -->
</activity>
<activity android:name=".ActivityC"
android:launchMode="singleInstance">
<!-- ActivityC has singleInstance launch mode -->
</activity>
<activity android:name=".ActivityD">
<!-- ActivityD will use flags for new task and clear top -->
</activity>
<activity android:name=".ActivityE">
<!-- ActivityE has no specific launch mode requirements -->
</activity>
Conclusion
- Tasks are a collection of activities that users interact with when performing a certain job.
- The Back Stack is the order in which activities are opened and stored in a task.
- Launch Modes determine how an activity instance is associated with a task and how it interacts with the back stack.
Launch modes allow developers to control the creation and organization of activities within tasks:
- Standard: Creates a new instance every time.
- Single Top: Reuses the top activity if it’s the same type being launched.
- Single Task: Ensures only one instance exists within a task, clearing others if necessary.
- Single Instance: Ensures one instance across all tasks, ideal for activities that must not be interrupted.
By choosing the appropriate launch mode, you can optimize the user experience by managing how activities are instantiated, reused, or cleared from memory. This knowledge helps in designing efficient, user-friendly app navigation flows.
Top comments (0)