A Task is a model that the Android system uses to manage a collection of Activities that the user is interacting with through some application's workflow. In the simplest, default case we would have a one-to-one mapping between one task and one application's set of Activities. When a user is navigating through a multi-activity application, each new Activity that is launched is added to the associated Task.
To keep everything organized, a Task places its Activities in a Back Stack; with each successively opened Activity being added to the top of the stack. This is also what helps facilitate the back navigation when the user presses the back button or swipes back; the current activity gets popped off the stack to be replaced with the Activity underneath it.
As noted earlier, one Task for one application's set of Activities is the default behavior. However, the Android framework does provide the developer with the power to manipulate this behavior either through the manifest file when declaring the Activity to the system, or by using Intent flags.
In this article, I want to focus on the Android manifest file and all the different ways in which we can manipulate the Activity-Task association behavior when defining different Activities in the app's manifest.
I've created a toy app to easily exemplify all the different behaviors. I will provide small code snippets where needed, but the full sample project can be found here on GitHub.
Project Setup
I created a brand new project using Android Studio's "Empty Views Activity" template. Then I created six new Activities:
ActivityAActivityBActivityCActivityDActivityEActivityF
Each Activity is basically the same in terms of layout and functionality. Their layout contains a TextView at the top to easily distinguish between each Activity. Then they also contain six buttons, each to open one of the Activities when clicked.
Also, in each Activity, I overrode the onNewIntent(...) function and displayed a Toast each time it was called. This function will be important later. An example of what it looks like in ActivityA:
class ActivityA : AppCompatActivity() {
// ...
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Toast.makeText(
this,
"onNewIntent called for Activity A",
Toast.LENGTH_LONG,
).show()
}
}
Lastly, instead of just renaming MainActivity, I updated the AndroidManifest.xml to make ActivityA the default launched Activity instead of MainActivity.
<manifest ...>
...
<application ...>
...
<activity
android:name=".launchactivities.ActivityA"
android:exported="true"
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="true">
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN" />-->
<!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>-->
</activity>
</application>
</manifest>
A Quick Note on a Task's Affinity
Each Activity, whether explicitly defined or not, has an Affinity. For a Task, its Affinity would be the Affinity for the Activity at the root of the Task. This holds even if there are multiple Activities with differing Affinities all in one Task (this is possible if all activities are using the standard Launch Mode that we'll discuss later). For example, say a Task has a Back Stack of the following Activities with Affinities:
A (.MyAffinity) -> B (.MyOtherAffinity) -> C (.YetAnotherAffinity)
Then the Affinity for the task would be .MyAffinity.
An Affinity denotes to Android which Task an Activity prefers to be in. By default, every Activity in an application has the same Affinity. However, there are no hard rules on which Affinity an Activity can have. All Activities in the same application can each have distinct Affinities, and even one Activity from one application can share an Affinity with an Activity from another application.
An Affinity is only one criterion for the OS to determine which Task to place a new Activity in. As we will see later, the Affinity combines with the Launch Mode to ultimately determine the placement of the newly opened Activity.
To set the Affinity of an Activity, you can use the taskAffinity XML attribute for the <activity> tag in the AndroidManifest.xml. If the taskAffinity attribute is not set, then the default Affinity is the value for the namespace property in the build.gradle file.
<manifest ...>
...
<application ...>
...
<activity
android:name=".launchactivities.ActivityB"
android:taskAffinity="my.Affinity"
android:exported="true"
</activity>
</application>
</manifest>
// build.gradle
android {
...
// default Affinity value
namespace = "com.nicholasfragiskatos.androidlaunchmodes"
}
Valid taskAffinity Values
The only rule I could find about appropriate values is from this part of the documentation where it says,
The
taskAffinityattribute takes a string value that must be different than the default package name declared in the<manifest>element.
However, I noticed that the value requires a period separator somewhere in the name. Having something like taskAffinity="MyAffinity" causes a build error:
Error running 'app': The application could not be installed: INSTALL_PARSE_FAILED_MANIFEST_MALFORMED
The following values seem to be OK:
.myAffinity
my.Affinity
myAffinity.
..myAffinity
. (yes, just a period)
Viewing Tasks on the System
I could not find a good graphical way to view all running Tasks on an emulator in Android Studio. There were some options, but they appeared to be old and incompatible with modern versions of the IDE. The best alternative I could find is using the Android Debugging Bridge (ADB) on the command line and looking through the output of the following command:
adb shell dumpsys activity recents
This will give you a list of recent Tasks, and show the list of Activities for the Task, as well as the Affinity, among other information. For example: (output formatted and abbreviated)
Recent tasks: Recent #0:{54288a5 #148 type=standard A=10164:.com.nicholasfragiskatos.androidlaunchmodes}
...
affinity=10164:com.nicholasfragiskatos.androidlaunchmodes
...
Activities=[
ActivityRecord{50c032f u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityA} t107},
ActivityRecord{eca6d90 u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityB} t107},
ActivityRecord{a3a3a7 u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityC} t107},
ActivityRecord{419e7d8 u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityD} t107}
]
Recent Numbering
The numbering changes based on which Task you viewed most recently. It's best to ignore the number and follow the Task ID (#148 from the example below):
Recent #0:{54288a5 # 148 type=standard A=10164:.com.nicholasfragiskatos.androidlaunchmodes}
Launch Modes
Launch Modes are a way for the user to specify to the system how a new instance of an Activity should be related to the current Task. We can specify a Launch Mode in either the AndroidManifest.xml when defining the Activity to the system or in Intent flags when launching the new Activity. There are five different Launch Modes.
standard
For this Launch Mode, a new instance of the Activity is created each time regardless of if there already exists an instance of the Activity in the Task. This is the default behavior. You can have as many of the same instances of an Activity across multiple tasks. For instance, all three of these examples are valid states when using the standard Launch Mode:
In the example project, to demonstrate this, launch ActivityA multiple times.
adb shell dumpsys activity recents output:
Recent tasks:
Recent #0:
...
Activities=[
ActivityRecord{449ced3 u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityA} t114},
ActivityRecord{5d70c45 u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityA} t114},
ActivityRecord{7a9c8d3 u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityA} t114},
ActivityRecord{948d5c1 u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityA} t114},
ActivityRecord{bffb7c9 u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityA} t114}
]
💡
By default, the new instance of the Activity will be created and placed in the same Task that it was launched from. However, as we will see later, depending on other factors it is not guaranteed.
singleTop
This Launch Mode is almost identical to standard. The only difference is that if the Activity you are trying to launch already has an existing instance at the top of the current Task's stack, then instead of creating a new instance, the system will just call the Activity's onNewIntent(...) function.
Considering the example below with the state of the stack being A -> B -> C -> D -> E,1 if we try and launch E again no new instance of ActivityE is created since it's at the top. Instead, the system re-uses the existing instance and just calls onNewIntent(...).2 However, when we try and launch ActivityC again, an instance already exists on the stack, but it's not at the top, so a new instance is created and added to the stack.3
In the example project, to demonstrate this, first, we set the Launch Mode in the manifest for ActivityE:
<manifest>
...
<application
...
<activity
android:name=".launchactivities.ActivityE"
android:launchMode="singleTop"
android:exported="false" />
...
<activity
android:name=".launchactivities.ActivityC"
android:launchMode="singleTop"
android:exported="false" />
</application>
</manifest>
Then, remember that we overrode the onNewIntent(...) function for each Activity to display a Toast:
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Toast.makeText(
this,
"onNewIntent called for Activity E",
Toast.LENGTH_LONG,
).show()
}
Starting Activities A -> B -> C -> D -> E -> E will result in the onNewIntent(...) function being called, but no additional instance of ActivityE is being added to the stack.
adb shell dumpsys activity recents output:
Recent tasks:
Recent #0:
...
Activities=[
ActivityRecord{f04dc64 u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityA} t118},
ActivityRecord{97af3cc u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityB} t118},
ActivityRecord{15ff5c9 u0 com.nicholasfragiskatos.androidlaunchmodes/.launchactivities.ActivityC



