Android Task and Back Stack Review

Android Task and Back Stack Review

Android Activities are the logical construct of the screens that we want a user to navigate through. The relation that each Activity holds with respect to other is very crucial for a good user experience. This relation should be designed with the focus of developing an effortless and pattern forming strategy from the user’s perspective.

One of the most important aspects of achieving the above is to design a proper forward and backward navigations. The Android design already has taken a very good care of providing a smooth user experience in terms of managing the screen flows.

Android developer guide says:
Each Activity should be designed around a specific kind of action the user can perform and can start other activities.

Let’s review the Android’s default implementations in the above regards.

Android defines the unit of a sequence of user interactions as Task. A Task is a collection of activities that user interact when performing a certain job.

A Task holds the Activities, arranged in a Stack called Back Stack. The Stack has LIFO structure and stores the activities in the order of their opening. The Activities in the Stack are never rearranged. The navigation of the back stack is done with the help of the Back button.

We will now go through the default behavior of the Task and the Back Stack.

  1. The application launcher creates a new Task with the main Activity created and placed in the root of the back stack (It has another role that we will review later).
  2. When the current Activity starts another Activity, then the new Activity is pushed on top of the stack and takes focus.
  3. The previous Activity moves below this new Activity in the back stack and is stopped. The system retains the current state of this Activity’s user interfaces like text in the form, scroll position etc.
  4. The subsequent new Activities keep on piling the back stack.
  5. When the Back button is pressed then the current Activity is popped from the top of the back stack. This destroys the Activity and the previous Activity resumes with its state restored.
  6. The Back button then keeps on popping the current Activities and restoring the previous activities. When the last Activity is removed from the back stack, then the Task terminates to the screen that was last running before the creation of the Task (in our case the launcher screen).
  7. When the back stack goes empty then the Task ceased to exist.
  8. Activities of different applications invoked by the intent are put into the same Task.

Task has few important properties:

  1. It goes into the background when a new task is created or when the Home button is pressed.
  2. It is then brought to the foreground by clicking the launcher icon (this is the another role of the launcher icon that we mentioned earlier) or by selecting the task from the recent screens.
  3. When multiple tasks are in the background or when the user leaves the Task for a long time, the system in order to recover memory clears the Task of all the Activities except the root Activity. When the user returns to the Task again, only the root Activity is restored (this behavior can be overridden as we will see later).

An Important Result:

We can derive from the above text a very important result. If an Activity is started from multiple Activities, then a new instance of that Activity is created and pushed on the stack rather than bringing the previous instance of that Activity to the top. In this case, the Back button reveals the instance of the same Activity multiple times with its state in the order is was created.

This particular behavior is undesirable and the main concern of this article is learning to control the back stack so that an Activity exists in a single instance at a time and with relation to its originator Activity. We will see how to do that in the text to follow.

There are few situations where we would want the manage the Task as a deviation from the default behavior. The Android Developer Guide lists some of those situations as follows:

  • When starting a new Activity, it is required to start in a new Task rather than being placed in the back stack of the existing Task.

As an example, the Android Browser application declares that the web browser Activity should always open in its own Task. This means that if your application issues an intent to open the Android Browser, its activity is not placed in the same task as your application. Instead, either a new task starts for the Browser or, if the Browser already has a task running in the background, that task is brought forward to handle the new intent.

  • When starting a new Activity the existing instance is required to be brought from the back stack as we already mentioned.
  • The back stack should be cleared to the root Activity when a user leaves the existing Activity.

In order to tackle the above deviations from the default, we are provided two modes.

  1. Using Android Manifest <activity> tag with attribute android:launchMode.
  2. Including Flags in the intent delivered to the startActivity().

The launchModes and its equivalent startActivity Flags allow us to define, how a new instance of an Activity is associated with the current Task and specifies the instruction for its launch in the given Task.

The types of launchModes and its equivalent startActivity Flags are presented and investigated in the following points (not all launchModes have its startActivity flag counterpart and vice versa):

  • launchMode — standard: This is the default mode of the Activity. In this mode, a new instance of the Activity is created and put in the Task which started it by routing the intent to it. The Activity in this mode can be instantiated multiple times, each instance can belong to a different task and one task can have its multiple instances.
  • launchMode — singleTop | flag — FLAG_ACTIVITY_SINGLE_TOP: This mode or flag produces exactly the same behavior as the standard launchMode if the new Activity in not already present in the back stack as the top. If it is present at the top then it behaves differently. In this case, the same Activity resumes with the call to its onNewIntentmethod.
  • launchMode — singleTask | flag — FLAG_ACTIVITY_NEW_TASK: If an Activity do not exist in an already created Task, then it starts the Activity in a new Task with Activity’s new instance at the root of the Task’s back stack, else the Task is brought forward with the Activity’s last state restored and this Activity receives the new intent in onNewIntent method. Only one instance of the Activity can exist at a time. In this case, the Back button is still able to return the user to the previous Task’s Activity.
  • launchMode — singleInstance: This launchMode is similar to the singleTask except that the System doesn’t launch any other Activity into the Task holding the instance. The Activity is always the single and the only member of its Task. Any Activity started by this one opens in a separate Task.
launchMode of singleTask and singleInstance are not appropriate for most of the applications.
  • flag — FLAG_ACTIVITY_CLEAR_TASK: This clears any existing task that would be associated with the Activity before the Activity is started. This Activity then becomes the new root of the task and old Activities are finished. It can only be used in conjunction with FLAG_ACTIVITY_NEW_TASK.

This particular flag is useful when the notification has to start the application by finishing the existing Activities. For example, if we want to start the SplashActivity from the Notification callback handler Service

Intent openIntent = new Intent(getApplicationContext(), SplashActivity.class);
openIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
getApplicationContext().startActivity(openIntent);
  • flag — FLAG_ACTIVITY_CLEAR_TOP: If the Activity being started is already running in the current task then instead of launching the new instance of that Activity, all the other activities on top of it is destroyed (with call to onDestroy method) and this intent is delivered to the resumed instance of the Activity (now on top), through onNewIntent method.

Note: For FLAG_ACTIVITY_CLEAR_TOP, if the launchMode is not defined in the AndroidManifest or set as “standard” for the Activity then the Activity along with its top is popped and a new instance of that Activity is placed on the top. So, onNewIntent method is not called.

This is undesirable, most of the time we would want to reuse the Activity and refresh it’s view states like, the data in the list, when it comes to the top of the back stack, rather than destroying and then recreating it.

In order to achieve this, we define the launchMode of the given Activity as singleTop and call the startActivity() with flag FLAG_ACTIVITY_CLEAR_TOP.

<activity
   android:name=".ActivityA"
   android:launchMode="singleTop"/>

And

Intent intent = new Intent(context, ActivityA.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
Precedence rule: If the current Activity adds the intent flag and the starting Activity has defined the launchMode then the current Activity’s request is honored.

Let’s explore few of the important Activity’s manifest attributes related to the back stack.

  1. noHistory: If this is set to true then it destroys the current Activity and removes it from the back stack whenever any other Activity is started by it. You don’t have to explicitly call finish for this activity. It is effective in SplashActivity.
  2. clearTaskOnLaunch: If this is set to true then the back stack is cleared down to the root Activity whenever the user leaves the Task and return to it.
  3. finishOnTaskLaunch: It is similar to clearTaskOnLaunch but operates on a single Activity.
Note: Don’t use startActivityForResult for Activities within the same app, rather use singleTop and FLAG_ACTIVITY_CLEAR_TOP as described above to tackle the parent view update when child view is removed.

Investigating Tasks and back stacks for the Application at run time:

In development phase, we should verify the application Task and back stack, if we have implemented the launchMode or have used the intent Flags. This can be done though the dump of the activity info.

Steps to follow for this particular investigation:

  1. Build and run the application in the mobile through Android Studio.
  2. Navigate few screens on the Mobile.
  3. Run adb shell dumpsys activity in the Studio’s terminal.
  4. This will give a long info text. Search for the package name of your application.
  5. Under the section ACTIVITY MANAGER ACTIVITIES we can find all the information about the task and activities in the back stack.

Also, Let’s become friends on Twitter, Linkedin, Github, and Facebook.

Coder’s Rock