Android Activity launchMode

In Android you can specify activity launchMode. You can do this either from AndroidManifest.xml file, either using intents with flags. Some of the flags produce the same effects like those from AndroidManifest.xml, but there are some flags that can be declared only with intents and also there are flags that can be declared only inside AndroidManifest.xml. In this tutorial we will talk only about those from manifest.

launchMode

There are 4 types of launchMode:

standard

Standard is the default launch mode used by the system. If you don’t set any launch mode to your activity, it will use the standard mode by default.

singleTop

We have the following scenarios:

We have 4 activities: A, B, C and D

Scenario 1

  • D activity has launchMode declared in AndroidManifest.xml as singleTop, the other 3 activities have standard
 <activity
        android:name=".D"
        android:launchMode="singleTop" />
  • the navigation of the activities is: A -> B -> C -> D. From D activity, we start activity D again.

Current Stack:

Question: What happens when we tap the back key?

Answer: The stack remains the same and the navigation is as follows: D -> C -> B -> A

Use case: Search function

Scenario 2

  • activity has launchMode=”singleTop”, the other 3 activities have standard
  • the navigation of the activities is: A -> B -> C -> D. From D activity, we start activity B.

Current Stack:

Question: What happens when we tap the back key in this scenario?

Answer: A new instance of B activity was added to the stack. The navigation is as follows: B -> D -> C -> B -> A

Conclusion

As you can see, singleTop flag, prevents instantiating a new existing activity only if the activity containing this flag, is the last one (at the top of the stack) like in Scenario 1.

singleTask

What is a task?

  • a task is a collection of activities
  • tasks can be seen in the Recent Apps from your device
  • an app can have multiple tasks
  • an activity declared with launch mode as singleTask can have only one instance in the system (singleton).
  • even though a new task is created, the Back button still returns the user to the previous activity

Use case

An example would be a web browser activity which should always open in its own task. This means that if you open a web browser from your app, it will open in a separate task from your app.

How does it work?

Let’s take an example. We open an app that was not opened recently, so the system creates a new task for this app, let’s say task1. You navigate through the app, and then press the Home button. At this point, the app goes in background, and all the activities are stopped, not destroyed, stopped. This means that the back stack of these activities remains intact. Now, if you open another app, with its own task (task2), and then put this one in the background and resume the first app (task1), you will see that you can pick up where you left off. The activity at the top of the stack will resume, while the other activities from the task remained intact. This means that if you press the Back button you will navigate back through the activities.

Create new task from same app

Scenario 1

We will use the same activities as earlier in this post (A, B, C and D) with the same navigation just that we will open C activity in a new task. The code is this:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ro.tutorial.funcode.backstack">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".A">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".B" />
        <activity
            android:name=".C"
            android:launchMode="singleTask"
            android:taskAffinity="" />
        <activity android:name=".D" />
    </application>

</manifest>

Important: Notice that we have to declare taskAffinity in order to open activity in a new task like the documentation states. Without taskAffinity, it will open in the same task (though the behavior of back stack might be different . See Scenario 4)

You can also see the task recording of the activities by using dumpsys activity command in the terminal from Android Studio (no logs or anything in the code):

adb shell 
dumpsys activity | grep -i run

And this what the above command will show us:

Running activities (most recent first):
        Run #3: ActivityRecord{925c2f0 u0 ro.tutorial.funcode.backstack/.D t268}
        Run #2: ActivityRecord{310b0c1 u0 ro.tutorial.funcode.backstack/.C t268}
        Run #1: ActivityRecord{1ea3b53 u0 ro.tutorial.funcode.backstack/.B t267}
        Run #0: ActivityRecord{db8c1c7 u0 ro.tutorial.funcode.backstack/.A t267}

As you can see, A and B activities are in task t267 while C and D are in a new separate task t268. C is the root activity for the new task.

Scenario 2

In this scenario, we will let the C activity to be the only one declared with launchMode as singleTask, but we will change the navigation through activities. This is the order:

A -> B -> C -> D -> B -> C

As you can see above, from D activity we will try to open B activity and then again. Let’s see what happens.

A -> B -> C -> D -> B 

Running activities (most recent first):
        Run #4: ActivityRecord{6c57756 u0 ro.tutorial.funcode.backstack/.B t306}
        Run #3: ActivityRecord{12c94b8 u0 ro.tutorial.funcode.backstack/.D t306}
        Run #2: ActivityRecord{791506e u0 ro.tutorial.funcode.backstack/.C t306}
        Run #1: ActivityRecord{ec3833b u0 ro.tutorial.funcode.backstack/.B t305}
        Run #0: ActivityRecord{bb50cc2 u0 ro.tutorial.funcode.backstack/.A t305}

At this point, we have 2 instances of B activity. One in task t305 and a new one in task t306. Let’s see what happens next.

A -> B -> C -> D -> B -> C

 Running activities (most recent first):
        Run #2: ActivityRecord{791506e u0 ro.tutorial.funcode.backstack/.C t306}
        Run #1: ActivityRecord{ec3833b u0 ro.tutorial.funcode.backstack/.B t305}
        Run #0: ActivityRecord{bb50cc2 u0 ro.tutorial.funcode.backstack/.A t305}

“Oh, well, what happened here?”, you might wonder. This happened:

  • C activity is declared as singleTask. This means that only one instance of this activity can exist in the system. So, the system brought the existing C activity in front of B from task t305.
  • in the process of bringing C activity in front of B, the system destroyed the above activities from task t306 (D and B from task t306)

Scenario 3

Let’s suppose that:

  • C and D activities are already in the background
  • D activity is declared with singleTask.
  • A and B activities are in a separate task.

What happens if we open D activity from B activity? See the image below:

As you can see, starting D activity declared as singleTask which already exists in background, from B activity, will move the whole stack of activities from D‘s task in front of B. Thus the back stack of the activities is now A – B- C – D.

Scenario 4

In this scenario, we will test what happens when we declare activity as singleTask, but without taskAffinity.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ro.tutorial.funcode.backstack">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".A">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".B"
            android:launchMode="singleTask"/>
        <activity
            android:name=".C"/>
        <activity
            android:name=".D"/>
    </application>

</manifest>

The navigation through activities is this:

A -> B -> C -> D

Running activities (most recent first):
        Run #3: ActivityRecord{9b4f543 u0 ro.tutorial.funcode.backstack/.D t340}
        Run #2: ActivityRecord{ab42b45 u0 ro.tutorial.funcode.backstack/.C t340}
        Run #1: ActivityRecord{27920b7 u0 ro.tutorial.funcode.backstack/.B t340}
        Run #0: ActivityRecord{bc03ead u0 ro.tutorial.funcode.backstack/.A t340}

Notice that activity, even though is declared with launchMode=”singleTask” it seems to be in the same task t340 as the other activities. This means that using singleTask will NOT always put the activity in a new task at the root as the documentation states. BUT declaring singleTask for is not completely redundant. Look what happens next, when we want to open B again from activity:

A -> B -> C -> D -> B

Running activities (most recent first):
        Run #1: ActivityRecord{904fa62 u0 ro.tutorial.funcode.backstack/.B t340}
        Run #0: ActivityRecord{9b9077c u0 ro.tutorial.funcode.backstack/.A t340}

and activities have been destroyed even if all activities were in the same task. This happens only if we open again an activity declared as singleTask, in our scenario B. If we had opened A activity, it would have been added on top of D.

Scenario 5

Now, let’s see what happens if we declare singleTask for activity D, without taskAffinity:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ro.tutorial.funcode.backstack">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".A">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".B" />
        <activity
            android:name=".C"
            android:launchMode="singleTask"
            android:taskAffinity="" />
        <activity
            android:name=".D"
            android:launchMode="singleTask"/>
    </application>

</manifest>

If we run the app we will notice that D activity is not in the same task as activity C, but it is in the same task as A and B activities.

Running activities (most recent first):
        Run #3: ActivityRecord{4429e75 u0 ro.tutorial.funcode.backstack/.D t280}
        Run #2: ActivityRecord{c923613 u0 ro.tutorial.funcode.backstack/.C t281}
        Run #1: ActivityRecord{9e67d4c u0 ro.tutorial.funcode.backstack/.B t280}
        Run #0: ActivityRecord{39499bf u0 ro.tutorial.funcode.backstack/.A t280}

singleInstance

This launch mode is the same as singleTask, except that an activity declared as singleInstance is always in a separate task and it is the only activity in that task. Any activities opened from this one, open in a separate task.

We will take an example.  The navigation through activities is this:

A -> B -> C -> D

C activity is declared singleInstance. Below is the stack.

Running activities (most recent first):
        Run #3: ActivityRecord{7925fc2 u0 ro.tutorial.funcode.backstack/.D t330}
        Run #2: ActivityRecord{ac1efc8 u0 ro.tutorial.funcode.backstack/.C t331}
        Run #1: ActivityRecord{f9f0862 u0 ro.tutorial.funcode.backstack/.B t330}
        Run #0: ActivityRecord{217e581 u0 ro.tutorial.funcode.backstack/.A t330}

As you can see, C activity is alone in a separate task, while A, B, D are in the same task.

taskAffinity

An activity can be set to prefer being in a certain task by using affinity. By default, all activities belong in the same task because they have an affinity for each other.

taskAffinity can be used in 2 cases:

  • when using launchMode singleTask
  • when using allowTaskReparenting

We will discuss only the first case in this tutorial.

taskAffinity with singleTask

In this case, the system looks for launching the activity in a new task. Often, it’a a new one, but it doesn’t always have to be a new one.  If there’s already an existing task with the same affinity as the new activity, the activity is launched into that task. Otherwise, it begins a new task.

  • the taskAffinity attribute takes a string as value which has to be declare with “.”, but has to be different from the package name of the app, because that is the default value of the system.
  • If you put an empty string “” as vaslue, will mean that that activity will not have an affinity for any task.
  • if you don’t add taskAffinity to your activity, it will inherit the affinity set for the application
keyboard_arrow_up
sponsored
Exit mobile version