This article is the original article, reproduced please indicate the source, the original is not easy, and turn and cherish

1. Introduction

The Activity stack management is another important function of AMS. The stack management is closely related to the start mode of the Activity and the Flag set by startActivity. The primary processing logic for Activity stack management is in the ActivityStart #startActivityUnchecked method, and this article will also go in and out of it, rubbing it in and out of it, until you can see its shape in your mind. Google’s engineers are still known for their impeccable naming system. Why are they wearing an Unchecked naming system? Unchecked- is uncertain, because it’s undecided which Activity I’ll launch when executing this method. Why, as I think you’ll see from this article.

2.Activity stack management related classes

2.1 ActivityStackSupervisor

As the name implies, the function provider and manager of the Activity stack

2.2. ActivityDisplay

Represents one screen. Android supports three screens: home screen, external screen (HDMI, etc.), and virtual screen (cast screen). In general, both ActivityStackSupervisor and ActivityDisplay are system unique when there is only the main screen

2.3. TaskRecord

ActivityTask is an important unit for managing the Activity stack, and its presentation is closely related to the logic and Activity launch mode, which is also the focus of this article

2.4. ActivityStack

Manage ActivityRecord and TaskRecord, record the status of ActivityRecord and TaskRecord. Before Android N, there were only two activitystacks :homeStack (Launcher and Recents Activity) and others. Android N starts with five different types of Activity, including DockedStack, PinnedStack, and FreeFormStack. Despite its name, ActivityStack, But it has very little to do with stacks as we know them in data structures, which may make it a little harder to understand

2.5. Diagram:

First, a word about relationships:

  • An ActivityDisplay contains multiple Activitystacks
  • An ActivityStack contains multiple TaskRecords
  • A TaskRecord contains multiple ActivityRecords

3. Start mode

3.1. Standard

In standard startup mode, add ActivityRecord to the top of the stack when you start an Activity and push it back when you return. Example :AActivity Open BActivity Open CActivity

This is the basic Activity launch mode, and there are not too many points to note

3.1.1. Intent. FLAG_ACTIVITY_CLEAR_TOP

If you open AActivity->BActivity->CActivity->DActivity, then the Intent adds a Flag when the DActivity starts AActivityIntent.FLAG_ACTIVITY_CLEAR_TOPThe system destroys all activities in a Task from BActivity to DActivity. Since DActivity is active, onPause is performed first. It will destroy the original AActivity, then open the new AActivity, and finally execute the onStop and onDestory of DActivity.

log:

3.1.2. Source code analysis

3.1.2.1. Join TaskRecord

ActivityStarter#startActivityUnchecked

if (mStartActivity.resultTo == null && mInTask == null && ! mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) ! = 0) { newTask = true; / / zhangyulong using an old Task, or create a new result = setTaskFromReuseOrCreateNewTask (taskToAffiliate preferredLaunchStackId, topStack); } else if (mSourceRecord ! = null) {// Zhangyulong uses the source Activity's task result = setTaskFromSourceRecord(); } else if (mInTask ! = null) {// Zhangyulong uses task result = setTaskFromInTask(); } else {/ / zhangyulong possible in theory, can't go here setTaskToCurrentTopOrCreateNewTask (); } if (result ! = START_SUCCESS) { return result; }Copy the code

Since we are starting in standard mode, resultTo and mSourceRecord are not empty. This logic will execute setTaskFromSourceRecord:

private int setTaskFromSourceRecord() {
       ...
        addOrReparentStartingActivity(sourceTask, "setTaskFromSourceRecord");
        return START_SUCCESS;
    }
Copy the code

Will eventually perform addOrReparentStartingActivity TaskRecord# addActivityAtIndex:

void addActivityAtIndex(int index, ActivityRecord r) { ... mActivities.add(index, r); . }Copy the code

Add this record to mActivities in the corresponding ActivityRecord to complete the TaskRecord

3.1.2.2. Standard + Intent. FLAG_ACTIVITY_CLEAR_TOP

Back to ActivityStarter#startActivityUnchecked, extract some of the logic as follows:

if (! mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) ! // Clear all activities on ActivityTask target Actiivty, The return value of top for the Activity can reuse ActivityRecord top = sourceTask. PerformClearTaskLocked (mStartActivity mLaunchFlags); mKeepCurTransition = true; // If the reusable Activity is not empty, call its onNewIntent method and resume if (top! = null) { ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTask()); deliverNewIntent(top); mTargetStack.mLastPausedActivity = null; if (mDoResume) { mSupervisor.resumeFocusedStackTopActivityLocked(); } ActivityOptions.abort(mOptions); return START_DELIVERED_TO_TOP; }}Copy the code

Focus on performClearTaskLocked, which is the logic for clearing the top element of the target Activity

/*** * newR: New Activity to start * launchFlags: */ Final ActivityRecord performClearTaskLocked(ActivityRecord newR, int launchFlags) { int numActivities = mActivities.size(); for (int activityNdx = numActivities - 1; activityNdx >= 0; --activityNdx) { ActivityRecord r = mActivities.get(activityNdx); if (r.finishing) { continue; If (r.realActivity.equals(newr.realActivity)) {final ActivityRecord ret = r; // Clear all activities above it for (++activityNdx; activityNdx < numActivities; ++activityNdx) { r = mActivities.get(activityNdx); if (r.finishing) { continue; } ActivityOptions opts = r.takeOptionsLocked(); if (opts ! = null) { ret.updateOptionsLocked(opts); } // Execute finishActivityLocked. If the Activity is stopped, onDestroy will be executed. If the Activity is still active, onPause if (mStack! = null && mStack.finishActivityLocked( r, Activity.RESULT_CANCELED, null, "clear-task-stack", false)) { --activityNdx; --numActivities; } } // ActivityInfo.LAUNCH_MULTIPLE == standrad // If the launchMode of the new Activity is standard and the launchFlag is not FLAG_ACTIVITY_SINGLE_TOP, then the Activity in the previous task // is terminated. If (ret. LaunchMode == activityInfo. LAUNCH_MULTIPLE && (launchFlags & Intent.flag_ACTIvitY_singLE_TOP) == 0 &&! ActivityStarter.isDocumentLaunchesIntoExisting(launchFlags)) { if (! ret.finishing) { if (mStack ! = null) { mStack.finishActivityLocked( ret, Activity.RESULT_CANCELED, null, "clear-task-top", false); OnNewIntent return null; } } return ret; } } return null; }Copy the code

FLAG_ACTIVITY_CLEAR_TOP: FLAG_ACTIVITY_CLEAR_TOP: FLAG_ACTIVITY_CLEAR_TOP

3.2. SingleTop

SingleTop: unique at the top of the stack. It differs from Standrad in that starting the same Activity at the top of the stack creates a new instance of the Activity. Launching the same Activity at the top of the stack only calls the original Activity’s onNewIntent. If the original Activity is not at the top of the stack, it behaves the same as the standrad

3.2.1. Flow chart:

3.2.1.1. The original Activity is not on the top of the stack

3.2.1.2. Original Activity is at the top of the stack

3.2.2. SingleTop + Intent. FLAG_ACTIVITY_CLEAR_TOP

Now imagine a scenario where AActivitylanchMode is singleTop, and BActivity and CActivity are successively opened on the basis of AActivity, and AActivity is opened again on CActivity. FLAG_ACTIVITY_CLEAR_TOP: intent.flag_activity_clear_top: intent.flag_activity_clear_top: intent.flag_activity_clear_top

3.2.3. The log

3.2.3.1. The original Activity is not on the top of the stack

3.2.3.2. The original Activity is at the top of the stack

3.2.3.3. Original Actiivty is at the top of the stack and set intent.flag_activity_clear_top

3.2.3. Source code analysis

We’re going to go back to ActivityStarter#startActivityUnchecked, startActivityUnchecked: you’re responsible for me 555…

The startActivityUnchecked method handles the singleTop mode as follows:

/ / to start the Activity at the right moment is the current in the current focus of the Activity / / on the top of the stack ActivityStack final ActivityStack topStack = mSupervisor. MFocusedStack; Actiivty Final ActivityRecord topstack.topActivity (); // Actiivty final ActivityRecord topstack.topActivity (); The Activity / / the current stack final ActivityRecord top = topStack. TopRunningNonDelayedActivityLocked (mNotTop); final boolean dontStart = top ! = null && mStartActivity.resultTo == null && top.realActivity.equals(mStartActivity.realActivity) && top.userId == mStartActivity.userId && top.app ! = null && top.app.thread ! = null && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) ! = 0 || mLaunchSingleTop || mLaunchSingleTask); DontStart (dontStart) {// For paranoia, make sure we have correctly resumed the top activity. topStack.mLastPausedActivity = null; if (mDoResume) { mSupervisor.resumeFocusedStackTopActivityLocked(); } ActivityOptions.abort(mOptions); if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) ! = 0) { // We don't need to start a new activity, and the client said not to do // anything if that is the case, so this is it! return START_RETURN_INTENT_TO_CALLER; } // ZhangYulong singleTop sigleTask onNewIntent execute deliverNewIntent(top); return START_DELIVERED_TO_TOP; }Copy the code

Note that when we set the target Activity launchMode to singleTop, we use mLaunchSingleTop to determine the condition. MLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP refers to the Flag attribute of the Intent set at launch. Both methods can achieve the effect of singleTop.

3.2.3.1. SingleTop + Intent. FLAG_ACTIVITY_CLEAR_TOP

The logic for this section is similar to that for Standard, but performClearTaskLocked, which has been analyzed in section 3.1.2.2. Here is some of the logic:

LAUNCH_MULTIPLE == standrad // If the launchMode of the new Activity is standard and the launchFlag is not FLAG_ACTIVITY_SINGLE_TOP, End the activity in the previous task //, If (ret. LaunchMode == activityInfo. LAUNCH_MULTIPLE && (launchFlags & Intent.flag_ACTIvitY_singLE_TOP) == 0 &&! ActivityStarter.isDocumentLaunchesIntoExisting(launchFlags)) { if (! ret.finishing) { if (mStack ! = null) { mStack.finishActivityLocked( ret, Activity.RESULT_CANCELED, null, "clear-task-top", false); OnNewIntent return null; }}Copy the code

Our launchMode is FLAG_ACTIVITY_SINGLE_TOP. If the launchMode is FLAG_ACTIVITY_SINGLE_TOP, the original Activity will not be destroyed, but all the records on the original Actiivty have been deleted, and it is already the top Activity on the stack. Continuing will execute the startActivityUnchecked method in Section 3.2.3, which is consistent with the above logic.

3.3 singleTask

SingleTask: stack unique. If an Activity has launchMode set to singleTask, then there is only one instance in the entire ActivityStack. Some friends see its name “singleTask” and assume that it is unique in the Task. Let’s not be fooled by its name. One thing to note is that singleTask mode starts with clearTop by default.

3.3.1 Startup process

The scene of a

Assuming that the launchMode of AActivity is singleTask, BActivity and CActivity are successively started after AActivity, and CActivity starts AActivity in turn, then the process goes through as follows:

log:

Scenario 2: Assuming that both AActivity and BActivity are Standard and CActivity is singleTask,CActiivty starts CActivity again. The startup flow of this process is as follows:

The log:

Scenario 3: After watching the first two scenarios, I’m sure some students will not be convinced. You said that singleTask is unique in the stack, and these two scenarios are processing in the task, so it should be unique in the stack. Don’t worry, the proof will come soon.

Now assume that AActivity is a singleTask and BActivity is standard. When BActivity starts CActivity, it creates a new Task, and then CActivity starts AActivity again as follows: 1.AActivity starts BActivity in its own task. 2.BActivity creates a new task when CActivity starts 4.AActivity Clears BActivity and restarts it

Flow chart:

Let’s see if log looks like this:

Log also verifies the correctness of this statement. From this case, we can see that although AB is in one task and C is in another task, when C starts A, it does not start its own task but operates AB’s task. Therefore, singeTask is unique in the stack.

3.3.2 Source code analysis of singeTask

Enter ActivityStarter#startActivityUnchecked once again, this time startActivityUnchecked inner activity: don’t enter, enter again you are pregnant!!

Why does singleTask come with the clearTop attribute? Take a look at the startActivityUnchecked logic:

// If the Intent is set to FLAG_ACTIVITY_CLEAR_TOP or the target Activity is singleInstance or singleTask. Execute the following logic if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP)! = 0 || isDocumentLaunchesIntoExisting(mLaunchFlags) || mLaunchSingleInstance || mLaunchSingleTask) { final TaskRecord task = reusedActivity.getTask(); / / will be the same as the new Actiivty within the Task of the Activity at the top of the element to empty final ActivityRecord top = Task. PerformClearTaskForReuseLocked (mStartActivity, mLaunchFlags); . }Copy the code

Where does the task switch to the top of the stack?

/ / the target Task Activity is moved to the stack reusedActivity = setTargetStackAndMoveToFrontIfNeeded (reusedActivity);Copy the code

Once the above logic is complete, the reusable Activity is already at the top of the Task, and the Task is already at the top of the Stack. If you continue with startActivityUnchecked, 3.2.3 is performed, which is consistent with singleTop. The onNewIntent of the target Activity is finally called back.

3.4 singleInstance

SingleInstance is a better startup mode than sigleTask. It is not only unique in the stack, but also owns a Task, which is a kind of boss with an independent office, completely different from other workers. For singleInstance stack management and switching, you can think of it as a Task with only one singleTask Activity, which we have analyzed above.

Intents.FLAG_ACTIVITY_NEW_TASK, taskAffinity, and new Task creation

Let’s look at some interesting cases: now there are two activities, AActivity and BActivity. Let’s set both A and B to standard and set them when A starts BIntent.FLAG_ACTIVITY_NEW_TASKWill the Task be created during a jump? Have a look at the log:We have seen from the figure, A and B of the TaskId is 305, that is not to create A new Task, how to return A responsibility, Intent. How did FLAG_ACTIVITY_NEW_TASK failure?

Set launchMode of B to “singleTask”? Have a look at the log:

Still not working!!

Leave B’s launchMode as singleTask and set B’s taskAffinity to “. B “.

It worked!!

Intent.flag_activity_new_task = “singleTask”; taskAffinity = “. B “;

To take effect!

Intent.FLAG_ACTIVITY_NEW_TASK = “.b”; intent. FLAG_ACTIVITY_NEW_TASK = “.b”;

No effect!

Delete taskAffinity from B and set B to singleInstace.

Working again!!

See here is feeling already dizzy, that after all when effective bask in when ineffectiveness ah! Don’t worry, we’ll make a summary after reading the source code

We’re going to go to activityStart #startActivityUnchecked again, startActivityUnchecked: Stop struggling, I’ve got your shape…

4.1 Automatic setting of intent.flag_activity_new_task

StartActivityUnchecked in front of a few lines of code to perform a method called computeLaunchingTaskFlags, this method is used according to the new Activity launchMode launchFlag for processing:

private void computeLaunchingTaskFlags() { ... if (mInTask == null) { if (mSourceRecord == null) { ... } else if (msourcerecord. launchMode == LAUNCH_SINGLE_INSTANCE) {// If the source Activity is singleInstance, New start when the Activity is automatically added launchFlag FLAG_ACTIVITY_NEW_TASK mLaunchFlags | = FLAG_ACTIVITY_NEW_TASK; } else if (mLaunchSingleInstance | | mLaunchSingleTask) {/ / if the new Activity is launchMode singleInstace or singleTask, New start when the Activity is automatically added launchFlag FLAG_ACTIVITY_NEW_TASK mLaunchFlags | = FLAG_ACTIVITY_NEW_TASK; }}}Copy the code

That is, if an Actiivty is singleInstacne, flag FLAG_ACTIVITY_NEW_TASK is automatically added whether it is started by someone else or if it is started by someone else. If it is singeTask, this is only set when it is started by someone else

4.2 taskAffinity identification

4.1 part of logic executed, startActivityUnchecked executes getReusableIntentActivity method, this method is mainly to find ActivityStack reusable Task, the return value will be the top of the reusable Task elements:

Private ActivityRecord getReusableIntentActivity () {/ / set the launchFlag FLAG_ACTIVITY_NEW_TASK or LaunchMode is singleInstance or singleTask Boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK)! = 0 && (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0) || mLaunchSingleInstance || mLaunchSingleTask; // inTask is null and requestCode is less than 0 (resultTo == NULL) putIntoExistingTask &= mInTask == null && mstartActivity. resultTo == null; ActivityRecord intentActivity = null; if (mOptions ! = null && mOptions.getLaunchTaskId() ! = -1) { final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId()); intentActivity = task ! = null ? task.getTopActivity() : null; } else if (putIntoExistingTask) { if (mLaunchSingleInstance) { // If launchMode is singleInstance, as long as the current Stack has the same record as the Activity to be launched, It can reuse intentActivity = mSupervisor. FindActivityLocked (mIntent, mStartActivity. Info, mStartActivity isHomeActivity ()); } else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) ! IntentActivity = = 0) {/ / not research mSupervisor. FindActivityLocked (mIntent, mStartActivity info,! mLaunchSingleTask); } else {/ / other cases to find whether there is applicable in the Stack of the Task and reusable Actiivty intentActivity = mSupervisor. FindTaskLocked (mStartActivity, mSourceDisplayId); } } return intentActivity; }Copy the code

The logic in findActivityLocked is relatively simple, just comparing activities through the Stack, Focus on ActivitySuperVisor# findTaskLocked, ActivitySuperVisor# findTaskLocked invokes the ActivityStack# findTaskLocked, look at the important logic:

void findTaskLocked(ActivityRecord target, FindTaskResult result) {
            ...
            } else if (!isDocument && !taskIsDocument
                    && result.r == null && task.rootAffinity != null) {
                // 如果Task的rootAffinity和新Activity的taskAffinity匹配,则说明有可复用的栈
                // ,task的rootAffinity一般由底部Actiivty决定,不特意设置的话,一般使用包名
                if (task.rootAffinity.equals(target.taskAffinity)) {
                    result.r = r;
                    result.matchedByRootAffinity = true;
                }
            } else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task);
        }
    }
Copy the code

This is where the taskAffinity matches. Came back and said the getReusableIntentActivity method return value of logic:

  • If launchMode is singleInstance, check whether the same Actiivty exists in the stack. If so, return the corresponding Actiivty; otherwise, null
  • If the launchMode is other, check whether there is a Task in the stack that matches its affinity. If so, return the Activity at the top of the corresponding Task; otherwise, null

4.3 Whether to Create a New Task

If getReusableIntentActivity method return value is not null, logic behind startActivityUncheck executes setTaskFromReuseOrCreateNewTask method:

private void setTaskFromIntentActivity(ActivityRecord intentActivity) { if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) { final TaskRecord task = intentActivity.getTask(); task.performClearTaskLocked(); MReuseTask = task; // Set the task to be used when starting the new Actiivty. mReuseTask.setIntent(mStartActivity); }... }Copy the code

Note that this method performs getReusableIntentActivity method is required for the return value is not null, the parameter intentActivity is getReusableIntentActivity methods return values. Therefore, if mReuseTask is null, a new task will be created when Actiivty is started, otherwise it will be added to mReuseTask as follows:

private int setTaskFromReuseOrCreateNewTask( TaskRecord taskToAffiliate, int preferredLaunchStackId, ActivityStack topStack) { mTargetStack = computeStackFocus( mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions); If (mReuseTask = = null) {/ / create a new Task final TaskRecord Task. = mTargetStack createTaskRecord ( mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mNewTaskInfo ! = null ? mNewTaskInfo : mStartActivity.info, mNewTaskIntent ! = null ? mNewTaskIntent : mIntent, mVoiceSession, mVoiceInteractor, ! mLaunchTaskBehind /* toTop */, mStartActivity.mActivityType); / / addOrReparentStartingActivity is added to the new Task (Task, "setTaskFromReuseOrCreateNewTask - mReuseTask"); . } else {/ / addOrReparentStartingActivity is added to the old task (mReuseTask, "setTaskFromReuseOrCreateNewTask"); }... return START_SUCCESS; }Copy the code

4.4 summarize

From the above code analysis, we can summarize the conditions for creating a new Task during the Activity startup process:

  1. FLAG_ACTIVITY_NEW_TASK and taskAffinity must be set at the same time in standard and singleTop modes
  2. In sinlgeTask mode, only taskAffinity is set. Intent.flag_activity_new_task is optional
  3. FLAG_ACTIVITY_NEW_TASK and taskAffinity are optional

Advertising time:

I opened a new wechat public account, the purpose is to share the most professional Android knowledge and industry information, welcome to follow: