The previous post of the Android Activities and Tasks series explained the concept of Android’s intents. We have seen how to use them to launch activities and how to utilize intent flags to customize the behavior of the launch to our needs.
In this post, we focus on activities themselves and explain the properties we can set on an activity or task to influence the activity launch behavior on the receiver side. In detail:
- activity launch modes
- task attributes
- task affinities of activities
In Android, every activity must be defined in the project’s AndroidManifest.xml file, using activity tags placed as children of the application tag. Here, a set of attributes can be specified for each activity. The only mandatory attribute is android:name, which defines the fully qualified name of the activity’s implementation class. This class must be a subclass of Activity. To avoid full qualification of the name, you can use the special “.” prefix, which will be interpreted by Android as the package name of the activity’s application. An example:
<activity android:name=".MyActivity" />
The intent flags (defined on the calling intent) in conjunction with the launch mode (specified on the called activity) define the actual outcome of the launch. There are four different launch modes: standard, singleTop, singleTask and singleInstance. The first two only have an impact on the activity, while the last two have important consequences for the task the activity should run in.
A new instance of the activity is created and pushed on top of the current task. The created instance will receive the intent. This is the default and most commonly used behavior.
Behaves exactly like standard, with one important exception: If an instance of the same activity is already on top of the task stack, then this instance will be reused to respond to the intent. No new activity instance is created. Note that, if such an instance is present in the stack, but not at the top, the behavior is identical to launch mode standard and will thus result in a new instance being created.
Although the callback onResume(..) is invoked on that activity instance, take note that getIntent() will still return the original intent, not the new intent! Thus, to receive the new intent, a singleTop activity must implement the method onNewIntent(Intent). This method is invoked by Android upon launch with the new intent object as parameter.
Using singleTop within an application is useful to avoid starting the same activity multiple times in a row. It will not enforce that you have only one instance of a certain activity type in one task stack, because an activity instance is only reused if it is at the top of the stack.
The activity is always started in a new task. Thus, a singleTask activity will always be the root activity of a task. Activities launched from this activity will be put on top of the new task’s stack by default.
Moreover, a singleTask activity acts as a singleton, i.e. it is impossible to have a second instance of such an activity, neither in this task nor in any other task. Upon launching the activity a second time, the intent is delivered to the existing instance via onNewIntent(Intent).
This behavior is useful for activities that act as entry points to your application. With this, you can enforce that these always start in a new task. At the same time, you ensure that the activity is never instantiated a second time and intents are delivered to the already existing activity.
Behaves exactly like singleTask but never allows other activities to become part of the new task. The activity will remain the sole activity instance in the task. As soon as another activity is started from here, the new activity will be automatically assigned to a different task, just like the intent would have FLAG_ACTIVITY_NEW_TASK set.
Use cases of this mode are rare. Any activity started from your singleInstance activity will be placed in another task. Thus, when the user returns to your singleInstance task, activities from the new task are not resumed and presented to the user, which is not desirable in the majority of cases.
It is useful for portal-like apps that only start activities from other applications, making it unnecessary to specify FLAG_ACTIVITY_NEW_TASK for all your intents and enabling the user to resume your portal app from the home screen without resuming activities that were started from there (as these will be put in other tasks automatically).
Besides for activities, Android allows you to set properties for tasks in order to customize the way the task should behave. These particular properties are therefore associated with a task and not an activity. But, as there is no direct representation of a task in the manifest XML structure, task attributes are defined as an attribute of the activity that is used as the root of this task. Thus, such a task property is only effective if it is defined on a task’s root activity; it is ignored in all other cases.
Android supports the following task attributes:
Normally, a task may be cleared by Android, meaning all activities besides the root activity are destroyed and removed from the task’s stack. After the clear has taken place, the user will see the task’s root activity when bringing the task to the foreground.
If a task has not been used in a while, Android automatically clears it. The assumption here is that, given that the task remained in the background for some time, the user is no longer interested in the activities she had navigated to. Instead, she wants to start at the root activity when bringing the task to the foreground.
The attribute alwaysRetainTaskState is a boolean value to prevent Android from clearing a task. If set to true, the activities on top of the root activity are always kept. This can be useful for tasks that carry a lot of state, like the tabs of a web browser, where you assume that the user wants to retain the state. Be aware that, as Android can no longer clear the task, the system may decide to kill your app entirely in order to reduce memory consumption.
The default is false.
When this boolean attribute is set to true, a task will be cleared everytime it is relaunched from the home screen. All activities on top of the root activity are removed. This means that, on relaunch, the task state is always discarded and the root activity is displayed to the user.
Thus, upon relaunch and when using this mode, activities on top of the root activity are usually finished. However, note that there is an important exception: If a to-be-removed activity allows reparenting (via attribute allowTaskReparenting) and this activity has an affinity for a different existing task, the activity will be moved to that task instead of being finished. We will address the topic of task reparenting and activity affinities in a minute.
Other task-related attributes
The following attributes are task-related, but define behavior for activities. As such, you can set them on any activity, not just on root activities.
If this boolean attribute is set to true, the activity is finished when the task is relaunched from the home screen. With this, you can mark an activity as irrelevant for the task state, meaning that we do not need to wait for Android to perform a task clean in order to forget this particular activity instance. Note, however, that the activity is not finished when bringing the task to the foreground using the list of recently started activities (by pressing and holding the home key).
If this boolean attribute is set to true, the activity instance never remains in the task stack. As soon as the user navigates away from it, the activity instance is immediately finished, and the user cannot return to it.
This does not only mean that the activity is no longer in the task stack upon returning (i.e. relaunching the app or resuming via the list of recently started activities). The user will also never be able to return to it via the back key.
Every activity has a so-called affinity for a certain task. You can think of an affinity as the name of a task this activity shall run in. By default, all activities within the same application are affine to the same task.
The task affinity of an activity has two consequences:
When launching the activity with the intent flag FLAG_ACTIVITY_NEW_TASK, Android looks for an already running task fitting this affinity, more precisely, a task whose root activity has the same affinity. If such a task exists, it is brought to the front, and the launched activity is pushed on top of its stack. If such a task does not exist, a new one is created, and activities launched at a later time will be put into this task, given that FLAG_ACTIVITY_NEW_TASK is used and the activity’s affinity is the same.
Suppose you are listening to a song within a music player application while have a running web browser application in the background. Now, you want to visit the website of the artist of the music track you are listening to. Let’s suppose that the music player fires an intent with FLAG_ACTIVITY_NEW_TASK to view the artist’s website inside a browser activity.
In our example, the browser application is already running in the background. As the browser activity belongs to the browser application and, as such, has an default affinity for the task the browser was launched into, the affinity will imply that the artist’s website will be displayed in a new activity that is pushed on the stack of the browser task, not the music player task.
If the activity’s property allowTaskReparenting is set to true, the activity can move from one task to another, namely from the one it was started in to the one it has an affinity for. This happens if this particular task is brought to the foreground, and an instance of our activity is present in another task at that time. Then, this activity is moved to the task that is coming to the front, and pushed on top of its stack.
Suppose you have a browsing activity that allows task reparenting and is capable of showing the contents of a web page. The activity is part of the browser application. Now, this activity is started from another application, your music player, to show the website of your favorite artist. Assuming the flag FLAG_ACTIVITY_NEW_TASK is not set, this will imply that the activity becomes part of the music player’s task.
If we now open the browser application, the activity (which has task reparenting enabled) will move from the music player task into the browser task. Why is that? Remember that the activities within an application share an affinity for the same task, which is why the browser activity is affine to the already existing browser task. Thus, upon opening the browser application, we are presented with the artist’s webpage we viewed earlier from within the music player application, and the browser activity is now on top of the browser task’s stack.
Android defines the following affinity-related attributes for activities:
An arbitrary string to define the task affinity; more specifically, the name of the task the activity preferrably runs in. When launched with intent flag FLAG_ACTIVITY_NEW_TASK, the activity is then put into this particular task, if present. Also, this value is used to determine the target activity for task reparenting.
The default value is the package name of the application this activity is defined in, which is why activities of the same application share an affinity for the same task by default.
A boolean value to allow or prohibit task reparenting. If set to true, running activities may be moved to the task they have an affinity for once this task is brought to the foreground.
In this post, we have seen that not only the caller of an activity can alter the launch behavior of an activity. Customizations of that behavior can also be defined on the receiver’s end, by setting a particular launch mode on an activity. In addition, we may influence the behavior of tasks by defining task attributes on them. Moreover, through usage of activity affinities, we can group related activities to allow Android to put them into the same task, if possible.
The next blog post will conclude this series with possible use cases and practical examples.