Public number: byte array, hope to help you 🤣🤣

The launchMode of an Activity itself is a tricky concept to understand, and most developers are probably limited to the four launchMode attribute values, but the launchMode is also affected by the Intent flag. And the Activity start mode is not only used to start an Activity, in fact, it will directly affect the user’s intuitive experience and use experience, because the start mode directly determines the application’s task stack and return stack, which are directly accessible to the user

This article will briefly introduce the startup mode of the Activity, hoping to help you 😇😇

1. Task stack

A task stack is a collection of activities that a user interacts with while performing a task, arranged in the order they are opened in a last-in, first-out stack. For example, an email application includes an Activity to display a mailing list. When the user selects an email, a new Activity will be opened to display the details of the email. This new Activity will be added to the task stack, pushed to the top of the stack and given focus, thus gaining the opportunity to interact with the user. When the user presses the Back key, the mail detail Activity is removed from the task stack and destroyed, and the mailing list Activity becomes the new top of the stack and regains focus

A task stack represents a whole and contains multiple activities. When all activities in the task stack are ejected, the task stack is reclaimed. As shown in the figure below, three activities form a stack of tasks by starting one after another. Activity 1 is the root Activity of the stack. When the user presses the back key repeatedly, the activities are popped up one by one

2. Return to the stack

The back stack is defined from the perspective of user use. The back stack contains one or more task stacks, but only one task stack is in the foreground at the same time. Only activities in the foreground stack can interact with the user

For example, if the user starts application A and then starts Activity 1 and Activity 2, Task A is the foreground Task stack. After that, the user clicks the Home button to return to the desktop, starts application B, and then starts Activity 3 and Activity 4. Task B becomes the foreground Task stack, and Task A becomes the background Task stack. When the user clicks the back button, the page will be displayed as Activity 4 -> Activity 3 -> Desktop

And if the user doesn’t go back to the desktop when they open app B, but instead launches App B directly from App A, When the user clicks the back button, the page displayed in sequence will be Activity 4 -> Activity 3 -> Activity 2 -> Activity 1 -> desktop

The back stack represents a collection of activities that the user sees as he or she continuously retreats to the page, which may be in several different task stacks. In the first case, the return stack contains only Task B, so when Task B is empty it goes straight back to the desktop. In the second case, the return stack contains Task A and Task B, so when Task B is cleared, Task A is returned to the desktop first, and Task A is returned to the desktop only when Task A is also cleared

It is important to note that there is no mandatory order between the stacks contained in the return stack, and changes in the stacking relationship between stacks can be detected at any time. For example, when application A starts application B, Task B is on top of Task A, but then if application B reversely starts application A, Task A becomes the foreground Task again and overrides Task B

3, taskAffinity

The concept of a return stack corresponds to taskAffinity, which is an attribute value declared in the Activity’s AndroidManifest file. TaskAffinity, translated as “affinity,” is used to indicate which task stack a particular Activity prefers to store itself in

By default, all activities in the same application have the same affinity, and all activities default to the current application applicationId as their taskAffinity property value. You can manually group certain activities within an application by specifying specific taskAffinity for those activities

        <activity
            android:name=".StandardActivity"
            android:launchMode="standard"
            android:taskAffinity="task.test1" />
        <activity
            android:name=".SingleTopActivity"
            android:launchMode="singleTop"
            android:taskAffinity="task.test2" />
        <activity
            android:name=".SingleTaskActivity"
            android:launchMode="singleTask"
            android:taskAffinity="task.test3" />
        <activity
            android:name=".SingleInstanceActivity"
            android:launchMode="singleInstance"
            android:taskAffinity="task.test4" />
Copy the code

Conceptually, activities with the same taskAffinity belong to the same task stack (they don’t have to). From the user’s point of view, they belong to the same “application”, because each taskAffinity has a separate list item in the recent task list, which looks like a separate application, when in fact these list items may come from the same application

4. Startup mode

The startup mode of an Activity is a very complicated difficulty, which determines the association between the Activity to be started, the task stack and the return stack, and directly affects the intuitive feeling of users

The launchMode is determined by both the launchMode and Intent flag, and can be defined in two ways:

  • Define the launchMode attribute value for your Activity in the AndroidManifest file. There are four types of attribute values
  • When usingstartActivity(Intent)When the Activity is started, theIntentAdds or sets the flag bit by which the startup mode is defined

It is not difficult to understand the four launchmodes, but when considering multiple application interactions and Intent flags, the situation becomes more complicated. The complexity and difficulties mainly include: A single task stack can contain activities from different applications, a single application can contain multiple task stacks, multiple task stacks contained in the return stack can be switched sequentially, and even activities in a task stack can be moved to another task stack. Intent flags can be used in multiple combinations

Some launch modes can be defined with launchMode but not with Intent Flag, and some launch modes can be defined with Intent Flag but not with launchMode. The Intent flags complement each other, but cannot completely replace each other, and the Intent flag has a higher priority

5, launchMode

LaunchMode contains the following four attributes:

  • Standard. Default mode. The system creates a new instance of the target Activity in the task stack that started the Activity, making the target Activity top of the task stack. In this mode, multiple target activities of the same type can be started successively. A task stack can have multiple target Activity instances, and different Activity instances can belong to different task stacks
  • SingleTop. If an instance of the target Activity already exists at the top of the current task stack, the system calls itonNewIntent()Method to forward the Intent to that instance and reuse it, otherwise a new instance of the target Activity is created. The target Activity can be instantiated multiple times, different instances can belong to different task stacks, and a task stack can have multiple instances (instances are not stacked consecutively).
  • SingleTask. If the system does not currently contain a target task stack for the target Activity, the system creates the target task stack first and then instantiates the target Activity as the root Activity of the task stack. If the system currently contains the target task stack and an instance of the target Activity already exists in the task stack, the system calls itonNewIntent()Method forwards the Intent to that existing instance without creating a new one, and pops up all other instances on top of the target Activity at the same time, making it top of the stack. If the system currently contains a target task stack that does not contain a target Activity instance, the target Activity is instantiated and pushed onto the stack. Therefore, only one instance of the target Activity can exist globally at a time
  • SingleInstance. withsingleTaskThe only difference is that an Activity started with singleInstance will have a task stack exclusive to it. It will not be placed in the same task stack as any other Activity. Any Activity started with this Activity will be opened in the other task stack

The four launchmodes are easy to understand, but one particular launchMode is singleTask, where activities that use the singleTask flag tend to store themselves in a particular task stack. If both the target task stack and target Activity already exist, they are reused; otherwise, they are created. SingleInstance adds an “exclusive task stack” feature on top of singleTask

An Activity started with singleTask is added to the back stack as shown in the figure below. When Activity 2 starts Activity Y in the background, the stack of Activity Y and Activity X is moved to the foreground. Overrides the current task stack. You end up with four activities on the stack

Let’s write a Demo to verify the four launchmodes

Declare four different launchMode activities, each with a different taskAffinity

        <activity
            android:name=".StandardActivity"
            android:launchMode="standard"
            android:taskAffinity="task.a" />
        <activity
            android:name=".SingleTopActivity"
            android:launchMode="singleTop"
            android:taskAffinity="task.b" />
        <activity
            android:name=".SingleTaskActivity"
            android:launchMode="singleTask"
            android:taskAffinity="task.c" />
        <activity
            android:name=".SingleInstanceActivity"
            android:launchMode="singleInstance"
            android:taskAffinity="task.d" />
Copy the code

Determine whether an instance of the Activity is being reused by printing the return value of the Activity’s hashCode() method, and determine which task stack the Activity is on by using the getTaskId() method

/ * * *@Author: leavesC
 * @Date: 2021/4/16 thus *@Desc:
 * @Github: https://github.com/leavesC * /
abstract class BaseLaunchModeActivity : BaseActivity() {

    override val bind by getBind<ActivityBaseLaunchModeBinding>()

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        bind.tvTips.text =
            getTip() + "\n" + "hashCode: " + hashCode() + "\n" + "taskId: " + taskId
        bind.btnStartStandardActivity.setOnClickListener {
            startActivity(StandardActivity::class.java)
        }
        bind.btnStartSingleTopActivity.setOnClickListener {
            startActivity(SingleTopActivity::class.java)
        }
        bind.btnStartSingleTaskActivity.setOnClickListener {
            startActivity(SingleTaskActivity::class.java)
        }
        bind.btnStartSingleInstanceActivity.setOnClickListener {
            startActivity(SingleInstanceActivity::class.java)
        }
        log("onCreate")}override fun onNewIntent(intent: Intent?). {
        super.onNewIntent(intent)
        log("onNewIntent")}override fun onDestroy(a) {
        super.onDestroy()
        log("onDestroy")}abstract fun getTip(a): String

    private fun log(log: String) {
        Log.e(getTip(), log + "" + "hashCode: " + hashCode() + "" + "taskId: " + taskId)
    }

}

class StandardActivity : BaseLaunchModeActivity() {

    override fun getTip(a): String {
        return "StandardActivity"}}Copy the code

SingleTaskActivity and SingleInstanceActivity are both in a separate task stack. StandardActivity and SingleTopActivity are on the same task stack. Note taskAffinity does not apply to standard and singleTop modes

E/StandardActivity: onCreate hashCode: 31933912 taskId: 37
E/SingleTopActivity: onCreate hashCode: 95410735 taskId: 37
E/SingleTaskActivity: onCreate hashCode: 255733510 taskId: 38
E/SingleInstanceActivity: onCreate hashCode: 20352185 taskId: 39
Copy the code

Then start SingleTaskActivity and SingleTopActivity in turn. You can see that SingleTaskActivity is reused and a new SingleTopActivity instance is launched on the task stack 38. The reason why SingleTopActivity is not reused is because the previous SingleTopActivity was in the 37 task stack, not the current task stack

E/SingleTaskActivity: onNewIntent hashCode: 255733510 taskId: 38
E/SingleTopActivity: onCreate hashCode: 20652250 taskId: 38
Copy the code

Start SingleTopActivity again, StandardActivity twice. You can see that SingleTopActivity is indeed reused in the current task stack, with two new StandardActivity instances created. In order for singleTop to be reused, the top of the current task stack is the target Activity, whereas standard creates a new instance each time

E/SingleTopActivity: onNewIntent hashCode: 20652250 taskId: 38
E/StandardActivity: onCreate hashCode: 252563788 taskId: 38
E/StandardActivity: onCreate hashCode: 25716630 taskId: 38
Copy the code

Start SingleTaskActivity and SingleInstanceActivity in turn. You can see that both SingleTaskActivity and SingleInstanceActivity are reused, and all three activities above SingleTaskActivity are removed from the task stack and destroyed. SingleTaskActivity is the new top-stack Activity for Task 38

E/StandardActivity: onDestroy hashCode: 252563788 taskId: 38
E/SingleTopActivity: onDestroy hashCode: 20652250 taskId: 38
E/SingleTaskActivity: onNewIntent hashCode: 255733510 taskId: 38
E/StandardActivity: onDestroy hashCode: 25716630 taskId: 38
E/SingleInstanceActivity: onNewIntent hashCode: 20352185 taskId: 39
Copy the code

Start StandardActivity and SingleTopActivity in turn. You can see that a new task stack is created and two new Activity instances are launched. Since the task stack on which the SingleInstanceActivity is located is exclusive to itself, StandardActivity starts by creating a new task stack to hold itself

E/StandardActivity: onCreate hashCode: 89641200 taskId: 40
E/SingleTopActivity: onCreate hashCode: 254021317 taskId: 40
Copy the code

To sum it up:

  • The taskAffinity attribute does not take effect in either Standard or singleTop modes. Activities launched in these two modes will always attempt to be added to the initiator’s task stack, or create a new task stack if the initiator is singleInstance
  • An Activity in Standard mode creates a new instance each time it is started, without any consideration for reuse
  • For a singleTop mode Activity to be reused, the Activity instance needs to be at the top of the launcher’s task stack
  • An Activity in singleTask mode is actually a global singleton of the system that is reused as long as the instance is not recycled. SingleTask can be started within a particular task stack by declaring taskAffinity and allowing other activities to share the same task stack together. If taskAffinity is not declared, attempts are made to find or actively create a task stack with taskAffinity as an applicationId, and then create or reuse activities within that task stack
  • SingleInstance can be seen as an enhanced version of singleTask, which monopolizes a task stack at any time, regardless of whether taskAffinity is declared. Other activities that are started in the singleInstance task stack are added to the other task stack

Note that the above results only apply when an Intent flag is not added. If an Intent flag is added at the same time, many strange things can happen

6, Intent flag

When starting an Activity, we can change the default association between the Activity and its task stack by setting multiple flags in the Intent that is passed to the startActivity(Intent) method. The priority of the Intent flag is higher than that of the launchMode

The Intent provides two methods for setting flags: override and increment

    private int mFlags;

	public @NonNull Intent setFlags(@Flags int flags) {
        mFlags = flags;
        return this;
    }

    public @NonNull Intent addFlags(@Flags int flags) {
        mFlags |= flags;
        return this;
    }
Copy the code

Add a flag and start the Activity as follows

        val intent = Intent(this, StandardActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
        startActivity(intent)
Copy the code

If an Activity’s launchMode is defined only by launchMode, the Activity’s launchMode cannot be changed at runtime and is written to death, so launchMode is suitable for businesses with fixed scenarios. Intent Flags exist to change or complement launchMode, and are suitable for scenarios that are mostly fixed, but rarely dynamic. For example, in some cases you don’t want singleInstance activities to be reused. This can be implemented dynamically with Intent flags

This makes it difficult to understand the logic of Intent flags, because Intent flags often need to be combined, and you need to consider various combinations of launchMode configurations that are not easily replaced

There are many Intent flags, of which four are common. This section briefly introduces these Intent flags

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_SINGLE_TOP
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_CLEAR_TASK

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_NEW_TASK is probably the most familiar flag to most developers, and one of the most common scenarios is to start an Activity in a non-ActivityContext. By default, the Android operating system adds the Activity to the launcher’s task stack. If the Activity is started by ServiceContext, the system is not sure how to store the target Activity. A RuntimeException is thrown

java.lang.RuntimeException: Unable to start service github.leavesc.launchmode.MyService@e3183b7 with Intent { cmp=github.leavesc.demo/github.leavesc.launchmode.MyService }: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
Copy the code

As you can see from the exception information, FLAG_ACTIVITY_NEW_TASK needs to be added to the Intent to start the Activity normally

        val intent = Intent(this, StandardActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        startActivity(intent)
Copy the code

FLAG_ACTIVITY_NEW_TASK also has an implicit implication that taskAffinity does not work in Standard or singleTop mode. However, this conclusion only applies when there is no active Intent flag

The combination of FLAG_ACTIVITY_NEW_TASK and standard mode can be summarized as follows:

  • Standard does not set taskAffinity. At this point, the system will either reuse or create a default task stack, and then directly create a new Activity instance on that task stack
  • Standard sets taskAffinity. In this case, it can be divided into two situations: whether the current system has a task stack associated with taskAffinity
    • There is no target task stack. At this point, the system creates a target task stack, and then pushes a new Activity instance directly onto the stack
    • Target task stack exists. At this point, the system determines whether an instance of the target Activity already exists in the task stack. If not, a new Activity instance is added to the stack. If there is a target instance, the task stack is simply rolled to the foreground without creating a new Activity instance or calling backonNewIntentMethod, even if the Activity instance is at the top of the stack, and does not respond as long as the same instance exists…

As you can see, the semantics of FLAG_ACTIVITY_NEW_TASK are somewhat obscure. This flag bit enables taskAffinity to create or reuse task stacks and bring them to the foreground, but it does not require you to create a new Activity instance. Instead, you only need an Activity instance to exist, and it doesn’t need to be at the top of the stack

FLAG_ACTIVITY_SINGLE_TOP

FLAG_ACTIVITY_SINGLE_TOP is a flag that is easily associated with singleTop, and in fact serves the same purpose as singleTop

Once this flag bit is added to the Activity to be launched and the top of the current task stack is the target Activity, the Activity instance is reused and its onNewIntent method is called back, even if the Activity declares standard mode. This is equivalent to overwriting the Activity’s launchMode as singleTop

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_CLEAR_TOP clears all activities above the target Activity. For example, if the current Activity to be started is already in the target task stack, setting this flag will clear all other activities on top of the target Activity, but the system will not necessarily reuse the existing target Activity instance. It is possible to create a new instance again after destruction

Let’s look at an example. StandardActivity and SingleTopActivity are started without flags, and the log information is as follows

E/StandardActivity: onCreate hashCode: 76763823 taskId: 39
E/SingleTopActivity: onCreate hashCode: 217068130 taskId: 39
Copy the code

Start StandardActivity again, FLAG_ACTIVITY_CLEAR_TOP. At this point, you’ll see that both activities that were started at the beginning are destroyed, anda new StandardActivity instance is added to the stack again

E/StandardActivity: onDestroy hashCode: 76763823 taskId: 39
E/StandardActivity: onCreate hashCode: 51163106 taskId: 39
E/SingleTopActivity: onDestroy hashCode: 217068130 taskId: 39
Copy the code

FLAG_ACTIVITY_SINGLE_TOP and FLAG_ACTIVITY_CLEAR_TOP will pop up SingleTopActivity and StandardActivity will be reused. And call back its onNewIntent method, the two flags are equivalent to combining the effect of singleTask. This is an effect the reader can verify for himself

FLAG_ACTIVITY_CLEAR_TASK

FLAG_ACTIVITY_CLEAR_TASK’s source code comment indicates that this flag must be used in conjunction with FLAG_ACTIVITY_NEW_TASK. Its purpose is to empty all activities in the target task stack, A new target Activity instance is then pushed onto the stack. This flag has a high priority, and even activities of the singleInstance type are destroyed

Let’s look at an example. Start SingleInstanceActivity first, then start SingleInstanceActivity again with NEW_TASK and CLEAR_TASK flags added. You can see that the old Activity instance is destroyed. A new instance is rebuilt, but the odd thing is that the onNewIntent method of the old Activity instance is also called

E/SingleInstanceActivity: onCreate hashCode: 144724929 taskId: 47
E/SingleInstanceActivity: onNewIntent hashCode: 144724929 taskId: 47
E/SingleInstanceActivity: onCreate hashCode: 106721743 taskId: 47
E/SingleInstanceActivity: onDestroy hashCode: 144724929 taskId: 47
Copy the code

7,

The launchMode and Intent flag combinations are a bit too difficult to fully describe, and there seem to be some version compatibility issues that make it even more difficult to use. So I think developers just need to get a general idea of what’s going on, and test it out for themselves when they’re actually going to use it, not memorize it

Each of the above examples can be Demo here: AndroidOpenSourceDemo

8. Reference materials

  • Learn about tasks and return stacks