It’s been about a month since my last blog post.

To summarize the last two weeks, I probably woke up and suddenly wanted to change jobs, and then I was slapped down by various interviewers

In addition to their own ability reasons, lack of preparation is indeed a big problem. So I want to take interview preparation as a long-term job and keep myself in a state ready for interview for a long time.

So, here’s a new column, Android Review Notes. Record my Android review road, also hope to help you.

As with relearning Kotlin, articles will be updated permanently in the mini-column. Portal:

xiaozhuanlan.com/android

Today I’m going to talk about the task stack and the back stack.

Task stack? Return stack?

I’ve read dozens of blog posts about the task stack and back stack, and the clearest thing I can say about it is that, aside from relearning Android, it’s not the official documentation. In fact, most of the time, a lot of vague basic concepts, from the official documents can easily get the answer you want.

The title of this section in the official document is Understand Tasks and Back Stack. A Task is what we call a Task Stack, and a Back Stack is a return Stack.

The task stack is easy to understand. Activities exist in a stack structure, last in, first out, which also fits the actual usage scenario.

Open Activity1, Activity2, and Activity3 in sequence, as shown in the figure above. Then press the back key twice in succession, and Activity3 and Activity2 will exit the stack in sequence.

What about the return stack?

What is a return stack?

What does the return stack do?

What is the difference between a return stack and a task stack?

Soul three even torture, do not know you can give a clear answer. Let’s hold down the table for a moment and look at the factors that affect the task stack and the return stack.

Let’s start with launchMode.

Boot mode

There are two ways to declare the startup mode:

  1. Declare the launchMode property of the Activity to be launched in the manifest file
  2. Set the flag when starting an Activity with an Intent

If both schemes are set during a startup, the latter has a higher priority.

The launchMode and Intent Flag of the manifest file cannot completely replace each other.

The launchMode attribute has four values: Standard, singleTop, singleTask, and singleInstance.

Standard: indicates the standard startup mode

This is also the default startup mode, where a new instance is created each time an Activity is started. When an Activity is started, it enters the task stack to which the source Activity belongs.

The same Activity can be instantiated multiple times.

SingleTop: Top of stack reuse mode

Instead of creating a new Activity that is already at the top of the source Activity’s stack, it uses the Activity at the top of the stack and calls back its onNewIntent methods. OnCreate and onStart are not called. Call onResume directly.

Otherwise, create a new Activity instance at the top of the stack.

SingleTask: In-stack reuse mode

The global singleton first looks for the desired task stack for the Activity to start (either by default or as specified by the taskAffinity attribute). If it does not find one, it creates a new task stack and places the Activity instance init. If the desired task stack is found, determine whether an instance of the Activity already exists in the stack. If it does, it pops the other Activity instances above the Activity and puts it at the top of the stack, also calling onNewIntent and onResume. If the instance does not exist, create a new one and push it onto the stack.

SingleInstance: single-instance mode

Global singleton, where a new instance of the Activity is created when it is first started and placed in a new task stack that contains only one instance. No new instance is created during subsequent startup.

The default Standard mode is fine for most cases, but creating multiple instances of the same Activity is definitely not appropriate in some cases, and the return stack can be obtrusive. In this case, you need to reuse existing Activity instances, so there are two different ways to reuse singleTop and singleTask. SingleInstance is more straightforward, with both the Activity instance and the task stack being globally unique.

Note also that the Activity instance of singleTask is also globally unique. One might ask, is it possible to have duplicate Activity instances that start in singleTask mode in different task stacks? But if you think about it, you can’t do that.

taskAffinity

As mentioned earlier, taskAffinity specifies the desired task stack for the Activity. But it doesn’t work in every situation.

Activities that do not explicitly declare taskAffinity have a default task stack named the application package name.

It does not work when the boot mode is set to Standard or singleTop. The Activity to be launched will follow the task stack of the source Activity, even if you explicitly declare a different taskAffinity.

When the startup mode is set to singleTask or singleInstance, it creates a new task stack to store the Activity instance to be launched.

In addition to singleTask and singleInstance, FLAG_ACTIVITY_NEW_TASK also enables taskAffinity, as described below.

Returns the meaning of the stack

Now that we know the basics, we can explore the existence and significance of the return stack.

The official website gives a good example to illustrate the existence of the return stack, but I won’t use the drawing on the official website, which is not very beautiful. I made a new one.

In the figure, dashed boxes represent the task stack and solid boxes represent the return stack.

Activity 1 and Activity 2 are in the foreground stack, that is, the one currently in focus, and both start in Standard mode. Activity X and Activity Y are in the background task stack and both start in singleTask mode. Starting Activity Y in the background task stack (cross-application launch) in Activity 2 at the top of the foreground task stack brings the entire background task stack to the foreground and puts it at the top of the back stack. At this point, the taskId of X and Y is consistent, and the taskId of 1 and 2 is consistent, and they are still in their respective task stacks, but the return stack is Y -> X -> 2 -> 1 from the top down. Pressing the Back button at this point will not return to Activity 2, but to Activity X first.

From the figure above, it is clear that the ** task stack and the return stack exist independently, and the return of the user page depends on the return stack, not the task stack. A back stack may contain activities from different task stacks to maintain the correct back stack relationship. ** That’s why the return stack exists.

What if both Activity X and Activity Y start in Standard mode? A new Y instance is created directly at the top of Activity 2’s stack, and Activity 2’s return stack is Y -> 2 -> 1. At this point, the return stacks of both applications do not interfere. The figure below shows the case where X and Y are both standard.

Similarly, singleTop did not perform as well as Standard.

Intent Flag

Another way to influence the launch mode, the task stack, and the return stack is to set the launch flag for the Intent.

There are two ways to set the startup flag:

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

One is set and one is add, so be careful when using it.

There are many Intent flags. Here, three classic ones are selected: NEW_TASK, CLEAR_TOP, and SINGLE_TOP.

FLAG_ACTIVITY_NEW_TASK

First, it doesn’t make sense to set FLAG_ACTIVITY_NEW_TASK alone without setting taskAffinity. No new task stack is created, and a new Activity instance is created each time the Activity is started and not reused within the stack.

By the way, why mention in-stack reuse? Isn’t that a feature of singleTask?

There are many articles on the web about the startup mode of an Activity that say:

The official documentation states that FLAG_ACTIVITY_NEW_TASK and singleTask behave in the same way. This is not true.

As I said earlier, just looking at this sentence, they do not behave consistently. So are official documents really conveying false perceptions?

Let’s take a look at the argument, which is exactly what the official document says:

Start the activity in a new task. If a task is already running for the activity you are now starting, that task is brought to the foreground with its last state restored and the activity receives the new intent in onNewIntent(). This produces the same behavior as the "singleTask" launchMode value, discussed in the previous section.

What it means is to start an Activity in a new task stack. If the desired stack already exists and the Activity to be started is already running in it, the stack is brought to the foreground and onNewIntent() is called back. This behavior is consistent with singleTask.

Using the example in the Significance of returning stacks section, Activity X and Y are both launched in standard mode with FLAT_ACTIVITY_NEW_TASK and taskAffinity is not set. In fact, it can also achieve the same basic return stack effect as singleTask.

But not exactly the same, the resulting return stack is Y -> Y -> X -> 2 -> 1. Consider the task stack and return stack below.

There will be two instances of Y, right? Standard, nothing wrong with it. SingleTask is fine, with only one instance. FLAG_ACTIVITY_NEW_TASK = “singleTask” = “singleTask”;

I can use singleTop, so that it really matches the example mentioned in the previous singleTask.

FLAG_ACTIVITY_NEW_TASK is not necessary if launchMode is set to singleInstance or singleTask. From the source code is also reflected.

StartActivity about flag in the process of calculation in ActivityStarter. Java classes in startActivityUnchecked () method of computeLaunchingTaskFlags () :

private void computeLaunchingTaskFlags(a){...if (mInTask == null) {
        if (mSourceRecord == null) {
            // 1. Start from a non-activity environment
            if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) { mLaunchFlags | = FLAG_ACTIVITY_NEW_TASK; }}else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
            // 2. The source Activity starts in SingleInstance mode
            mLaunchFlags | = FLAG_ACTIVITY_NEW_TASK;
        } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
            // 3. Start the Activity in singleInstance or singleTask modemLaunchFlags | = FLAG_ACTIVITY_NEW_TASK; }}}Copy the code

As you can see from comment 3 above, FLAG_ACTIVITY_NEW_TASK is automatically added when the startup mode is singleInstance or singleTask.

FLAG_ACTIVITY_NEW_TASK is probably better known for starting an Activity from a non-activity environment.

By default, the Activity to be started goes to the task stack where the source Activity resides. If it is started from a non-activity environment, such as Service, Broadcast, Application, etc., there is no corresponding task stack, and AMS cannot infer which task stack to put the Activity in. Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.

The exception is in ContextImpl startActivity () method from them:

    @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        final int targetSdkVersion = getApplicationInfo().targetSdkVersion;

        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && (targetSdkVersion < Build.VERSION_CODES.N
                        || targetSdkVersion >= Build.VERSION_CODES.P)
                && (options == null
                        || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                            + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                            + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }
Copy the code

* * first detects whether set up FLAG_ACTIVITY_NEW_TASK, if set, will call Instrumentation. ExecStartActivity ().

FLAG_ACTIVITY_NEW_TASK tells the Activity to be started to be added to a new task stack. Whether or not a “new” task stack is actually determined by taskAffinity, as discussed earlier. Therefore, an Activity that does not declare taskAffinity is displayed, and in a non-activity environment that is just started with FLAG_ACTIVITY_NEW_TASK, it will still be in the default task stack.

FLAG_ACTIVITY_CLEAR_TOP

When CLEAR_TOP is used alone, if an instance of the Activity to be started already exists in the desired task stack, it pops other activities above that Activity instance, puts itself at the top of the stack, and calls back to onNewIntent. The launchMode of the Activity to be launched cannot be standard.

If it is Standard, it will pop up all activities above it and create a new instance.

FLAG_ACTIVITY_SINGLE_TOP

Same as singleTop, top of stack reuse. Even if the Activity to be started is standard, it will be reused if it is already at the top of the stack.

Next, we’ll look at some of the Activity properties that can be used in manifest files to control the task stack and return the stack.

The Activity attribute

allowTaskReparenting

Allows transfer of task stacks. According to official documents and various web articles, it is supposed to work like this:

Go from page A of App1 to page B of App2, and page B has allowTaskReparenting=true. At this point, page A starts page B, so page B is in the task stack of App1. Then click the Home button to return to the desktop, and then click the desktop icon of App2, which should launch page B. This is equivalent to page B moving from the task stack of App1 to the task stack of App2.

But the truth is, I didn’t reproduce the scene. My test environment was like this: since page A and page B were from two different apps, I didn’t specifically set taskAffinity because it was different. The startup mode of page B is Standard.

The operation steps are as follows:

App1 page A – > App2 page B – > Home button – > App2 Launcher

Instead of page B, the test pops up the home page of App2. I don’t know if there’s anything wrong with my test procedures. You can also test it out.

Changing the launch mode of page B to singleTask can have a similar effect. But page B does not go to page A’s task stack, which is the effect of singleTask + taskAffinity, and has nothing to do with allowTaskReparenting.

I don’t know what you think of this property, but let me know in the comments.

= = = line

Updated on 4 August 2020:

Finally, the proper use of allowTaskReparenting – the stack shift method.

In my sample code in the demo, for example, AllowTaskReparentingActivity is the Activity of App1, package name, or the default stack is luyao. Android, But we give it a different taskAffinity — luyao.android2, the default task stack for App2, and allowTaskReparenting=”true”, as follows:

<activity android:name=".activity.AllowTaskReparentingActivity"
           android:label="AllowTaskReparenting"
           android:taskAffinity="luyao.android2"
           android:allowTaskReparenting="true"/>
Copy the code

The operation process is shown in the Gif below:

Experience in App1 StandActivityA – > AllowTaskReparentingActivity, can see the taskId is consistent, stack them in the same task. Why is taskAffinity set and still in the same task stack? Since the default startup mode is Standard, taskAffinity does not work. But it doesn’t seem to be completely useless, so let’s move on.

Then press the Home button to return to the desktop. Start App2 again. Under normal circumstances should start App2 MainActivity, but as a result of the action of allowTaskReparenting, start here is AllowTaskReparentingActivity, press the return key at this time, I’m back to MainActivity in App2.

Careful readers might also saw, standard mode, the App2 AllowTaskReparentingActivity and MainActivity is not in the stack with a task, and the so-called task stack transfer does not occur, just made some processing to the back stack. Interested readers can clone the code to try out other boot modes.

So, how specifically can allowTaskReparenting be used? I don’t know about that either. The above example code doesn’t look exactly the same in my MIUI and the native ROM in the Android VIRTUAL machine, let alone the various mobile vendors.

clearTaskOnLaunch

If the root Activity of the task stack is set to clearTaskOnLaunch=true, then when pressing the Home button to return to the desktop and re-clicking the desktop icon to enter the application (entering from the recent task list does not work), All other activities above the root Activity pop up, leaving only yourself.

If the Activity setting clearTaskOnLaunch=true is not at the bottom of the task stack, it has no effect.

alwaysRetainTaskState

In contrast to clearTaskOnLaunch, what it does is try to keep all instances in the task stack from being destroyed. With alwaysRetainTaskState not set by default, the task stack may be cleared for a variety of reasons, leaving only the root Activity.

It also works only when set at the root of the Activity, and is useless for other activities.

finishOnTaskLaunch

The same effect as clearTaskOnLaunch, but only for the current Activity with finishOnTaskLaunch=true. When you press the Home button to return to the desktop and then click the desktop icon to re-enter the application, the Activity finishOnTaskLaunch= True will be removed from the task stack.

All of its activities are valid, including the root Activity.

excludeFromRecents

Whether the current Activity’s task stack is displayed in the latest task list. Only Settings on the root Activity have an effect.

autoRemoveFromRecents

Let me ask you A question, go to the App, jump from A to B, from B to C, and press back until you get back to the desktop. Check the recent task list at this time, can you see this App in it? The answer is yes.

The function of autoRemoveFromRecents is that when all activities in the stack are removed, they are automatically removed from the recent task list.

The last

The launch mode, taskAffinity, and Intent Flag properties combine to create a variety of task stacks and return stacks.

Don’t believe the conclusions of any web articles, including what I said above. Do it yourself, to calmly face the interviewer’s various “permutations”. If you really don’t want to type, I have a sample code here that contains all the validation code I’ve done in the course of writing this article.

Matching code portal


Silence for more than a month, I came back to export. Focus on me, don’t get lost!