directory
- preface
- Get into the topic and think about the problem
- Start mode source code analysis
- 2.1 Initialization
- 2.2 getResuableIntentActivity
- 2.3 The most appropriate reusable stack
- 2.4 reusedActivity processing
- 2.5 Determining the singleTop mode
- 2.6 Stack reuse and creation
- Startup Mode Flow chart
- conclusion
preface
This is also a question often asked in the interview process, there are many online interview questions, but the source analysis of the article is more difficult to find, so I will analyze it, also hope this article can be helpful to the interviewer ~
First, get into the topic and think about the problem
Before analyzing the source code, let’s think about a few problems first. After the analysis is completed, we can see if it can be solved
- If two applications A and B have two activities in A, A1 and A2, and one Activity B1 in B, the three activities are SingleTask mode, the launch sequence is A1 -> B1 -> A2, and then click the back button, what will happen?
- What if A2 is Standard + FlAG_ACTIVITY_NEW_TASK?
- What does FLAG_ACTIVITY_NEW_TASK do? What is taskAffinity?
Two. Start mode source code analysis
The startActivityUnchecked method in the activitystarter.java file is used to check the startup mode.
Note the next two important attributes, mLaunchFlags and mLaunchMode, which are discussed in the source code analysis
FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_CLEAR_TASK, etc. These flags specify how to start an Activity, stack selection, and handling.
MLaunchMode Indicates the startup mode, such as LAUNCH_SINGLE_INSTANCE and LAUNCH_SINGLE_TASK.
2.1 Initialization
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity) {
// Initialize mLaunchFlags mLaunchMode
setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
voiceInteractor);
// calculate mLaunchFlags
computeLaunchingTaskFlags();
/ / assignment mSourceTaskcomputeSourceStack(); mIntent.setFlags(mLaunchFlags); . . .Copy the code
See computeLaunchingTaskFlags method
private void computeLaunchingTaskFlags(a) {... . .// mSourceRecod refers to the initiator (note the difference between mStartActivity). MSourceRecord is null, indicating that we are not starting an Activity
// May be from a Service or ApplicationContext
if (mSourceRecord == null) {
if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
//mInTask mSourceRecord is null, which means that one Activity is not started from another Activity, so whatever
// FLAG_ACTIVITY_NEW_TASKmLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; }}else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
// If the initiator is SINGLE_INSTANCE, mLaunchFlags flags FLAG_ACTIVITY_NEW_TASK regardless of the mode in which the Activity is started.
// This new Activity needs to run on its own stack
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
} else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
// launchMode is SINGLE_INSTANCE or SINGLE_TASK; Add FLAG_ACTIVITY_NEW_TASK mLaunchFlagsmLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; }}Copy the code
2.1 Summary
Note that FLAG_ACTIVITY_NEW_TASK has been added to mLaunchFlags in three places
- If the initiator mSourceRecord is null, such as when an Activity is started in a Service, FLAG_ACTIVITY_NEW_TASK is added to mLaunchFlags
- If the initiator mSourceRecord is an Activity of type SingleInstance, then the mLaunchFlags of the initiated Activity will be FLAG_ACTIVITY_NEW_TASK
- If the initiated mStartActivity is SINGLE_INSTANCE or SINGLE_TASK, the mLaunchFlags of the initiated mStartActivity are FLAG_ACTIVITY_NEW_TASK
Here’s a question to consider
What is the difference between starting an Activity in a Service or Application and starting an Activity in an Activity? (Actvivity overwrites ContextWrapper’s startActivity method. The Service call is essentially ContextImpl’s startActivity method.)
2.2 getResuableIntentActivity
This section is also a fragment of the startActivityUnchecked method, followed by the source code of the first section
// Still inside startActivityUnchecked. . .// 1. Find reusable activities for SINGLE_INSTANCE
FLAG_ACTIVITY_NEW_TASK and no MULTI_TASK
// 3. SINGLE_TASK searches for stacks that can be addedActivityRecord reusedActivity = getReusableIntentActivity(); . . .Copy the code
We also jumped out startActivityUnchecked methods, by the way, take a look at getResuableIntentActivity method.
private ActivityRecord getReusableIntentActivity(a) {
//putIntoExistingTask is true
//(1) When start mode is SingleInstance;
//(2) When the startup mode is SingleTask;
FLAG_ACTIVITY_MULTIPLE_TASK = intent.activity_task = intent.activity_task = intent.activity_task
booleanputIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) ! =0 &&
(mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
ActivityRecord intentActivity = null;
if(mOptions ! =null&& mOptions.getLaunchTaskId() ! = -1) {... . . }else if (putIntoExistingTask) { // The policy when putIntoExistingTask is true
if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
// go to SINGLE_INSTANCE mode and findActivityRecord
intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
mStartActivity.isActivityTypeHome());
} else if((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) ! =0) {... . }else {
// This is different from the method called by singleInstance. The purpose here is to findTaskRecord
// Otherwise find the best task to put the activity in.intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId); }}return intentActivity;
}
Copy the code
2.2 Summary
The following figure shows three cases where putIntoExistingTask is true
When putIntoExistingTask is true, we can continue looking at the code and draw another conclusion:
MLaunchMode of SingleInstance, walk mSupervisor. FindActivityLocked; Other cases, such as our mStartActivity is a standard model of the Activity, and only added FLAG_ACTIVITY_NEW_TASK flag, will go mSupervisor. FindTaskLocked
Here we can vaguely guess from the name of the method. In the case of SinglgeInstance, we are looking for the same activityRecords; In other cases, we find the best stack (as you can see from the comments). In fact, we were right. In the case of SingleInstance, the source code is also iterated to find the same ActivityRecord. But what about other cases? Let’s start with the question, what is the most appropriate stack? What kind of conditions does it have to satisfy?
2.3 The most appropriate reusable stack
From mSupervisor findTaskLocked into, we finally tracked down ActivityStack. Java
void findTaskLocked(ActivityRecord target, FindTaskResult result) {... . .// Note that mTaskHistory is traversed backwards
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { ... . . }else if(! isDocument && ! taskIsDocument && result.r ==null&& task.rootAffinity ! =null) {
// Check if the taskAffinity is the same
if (task.rootAffinity.equals(target.taskAffinity)) {
// When we find a stack that matches taskAffinity, we do not break the stack immediately, but continue to search, indicating that the smaller the task index is, the more suitable it is
result.r = r;
result.matchedByRootAffinity = true; }}else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: "+ task); }}Copy the code
2.3 Summary
The most suitable stack satisfies two conditions
1. The Activity taskAffinity is equal to the rootAffinity of our task
2. Different tasks may have the same rootAffinity, and the most appropriate one is the one with the smallest index
2.4 Processing of reusedActivity
We then analyze the startActivityUnchecked code
// Make use of activities or stacks that can be reused
if(reusedActivity ! =null) {... . .// If it is SingleInstance or SingleTask or FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_CLEAR_TOP // We can identify FLAG_ACTIVITY_CLEAR_TOP SingleInstance or SingleTask
if((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) ! =0
|| isDocumentLaunchesIntoExisting(mLaunchFlags)
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
// Get the stack of reuseActivity
final TaskRecord task = reusedActivity.getTask();
// For example, SingleTask removes all activities prior to the Activity being started
final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
mLaunchFlags);
if (reusedActivity.getTask() == null) {
reusedActivity.setTask(task);
}
if(top ! =null) {
if (top.frontOfTask) {
// Activity aliases may mean we use different intents for the top activity,
// so make sure the task now has the identity of the new intent.
top.getTask().setIntent(mStartActivity);
}
// Here is SingleInstance or SingleTask, which executes onNewIntentdeliverNewIntent(top); }}... . .// The initiator and the initiated are the same
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! And for paranoia, make sure we have
// correctly resumed the top activity.
resumeTargetStackIfNeeded();
return START_RETURN_INTENT_TO_CALLER;
}
if(reusedActivity ! =null) {
SingleTask singleInstance and singleTop
setTaskFromIntentActivity(reusedActivity);
if(! mAddingToTask && mReuseTask ==null) {
//singleInstance singleTask goes here
//1. For example, the Activity to be started is singleTask and is in the reusedActivity stack
//2. Or an Activity in singleInstance mode is started again
resumeTargetStackIfNeeded();
if(outActivity ! =null && outActivity.length > 0) {
outActivity[0] = reusedActivity;
}
returnmMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP; }}}Copy the code
Continue to look at the setTaskFromIntentActivity this method
private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TASK
final TaskRecord task = intentActivity.getTask();
/ / clear the task
task.performClearTaskLocked();
mReuseTask = task;
mReuseTask.setIntent(mStartActivity);
} else if((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) ! =0
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
// If it is SingleInstance or SingleTask, clear the stack of all activities before the Activity to be started.
ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
mLaunchFlags);
// If top == null continues without null, the method is terminated
if (top == null) {... . . }}else if (mStartActivity.realActivity.equals(intentActivity.getTask().realActivity)) {
// Check whether the mode is SingleTop
// How can this situation be repeated? SingleTop + FLAG_ACTIVITY_NEW_TASK + taskAffinity.
// FLAG_ACTIVITY_NEW_TASK + taskAffinity specifies a specific existing stack at the top of which is the singleTop activity we want to start
if(((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) ! =0
|| LAUNCH_SINGLE_TOP == mLaunchMode)
&& intentActivity.realActivity.equals(mStartActivity.realActivity)) {
if (intentActivity.frontOfTask) {
intentActivity.getTask().setIntent(mStartActivity);
}
deliverNewIntent(intentActivity);
} else if(! intentActivity.getTask().isSameIntentFilter(mStartActivity)) { ... . . }}else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {... . . }else if(! intentActivity.getTask().rootWasReset) { ... . . }}Copy the code
2.4 Summary
If reusedActivity is not null, look at two typical cases:
1. If the Activity is in SingleTask or SingleInstance mode, execute performClearTaskLocked to remove all activities prior to the Activity to be started.
2. The launch mode of reusedActivity happens to be SingleTop, which is also the Activity we want to start, executing deliverNewIntent.
The two cases above satisfy the following if judgment! MAddingToTask && mReuseTask == NULL, then return ends.
Think of a problem, situation 1 and 2 as examples to emerge?
2.5 Determining the SingleTop mode
Continue looking at the code behind startActivityUnchecked, which deals with the SingleTop mode.
Note oh, SingleTop mode of processing, we also have the setTaskFromIntentActivity method is just above. What’s the difference between these two?
The difference here is that the stack judged by the SingleTop pattern is the stack we are currently showing. And setTaskFromIntentActivity judgment precondition is in our reusedActivity isn’t empty, judgment of reusedActivity reusedActivity may not be the current stack.
. . .If the Activity you want to start happens to be at the top of the stack, check to see if it only needs to be started once
// If the activity being launched is the same as the one currently at the top, then
// we need to check if it should only be launched once.
final ActivityStack topStack = mSupervisor.mFocusedStack;
final ActivityRecord topFocused = topStack.getTopActivity();
final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
final booleandontStart = 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
|| isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK));
// SINGLE_TOP SINGLE_TASK, the Activiy to start happens to be at the top of the stack
// If dontStart is true, new activities will not be started and the activities at the top of the stack will be reused
if (dontStart) {
// For paranoia, make sure we have correctly resumed the top activity.
topStack.mLastPausedActivity = null;
if (mDoResume) {
// resume Activity
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;
}
deliverNewIntent(top);
// Don't use mStartActivity.task to show the toast. We're not starting a new activity
// but reusing 'top'. Fields in mStartActivity may not be fully initialized.
mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredWindowingMode,
preferredLaunchDisplayId, topStack);
return START_DELIVERED_TO_TOP;
}
Copy the code
2.5 Summary
This part of the code deals with SingleTop mode activities. The Activity to be launched is SingleTop mode and also happens to be at the top of the current stack, executing the deliverNewIntent.
Consider a quick question: Start an Activity in SingleTop mode, and then start it again. How does its life cycle change?
OnCreate -> onStart -> onResume -> onPause -> onNewIntent -> onResume.
2.6 Stack reuse and creation
Move on to the last part of the startActivityUnChecked method, which is concerned with whether the stack needs to be created.
. . .// FLAG_ACTIVITY_NEW_TASK is required
if (mStartActivity.resultTo == null && mInTask == null&&! mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) ! =0) {
FLAG_ACTIVITY_NEW_TASK is required to create a new stack or to use an existing stack
newTask = true;
result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack);
} else if(mSourceRecord ! =null) {
// Place the started mStartActivity on the stack where the initiator mSourceRecord is locatedresult = setTaskFromSourceRecord(); }... . .Copy the code
SetTaskFromReuseOrCreateNewTask method
private int setTaskFromReuseOrCreateNewTask( TaskRecord taskToAffiliate, ActivityStack topStack) {
mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);
if (mReuseTask == null) {
// Create a new stack
finalTaskRecord task = mTargetStack.createTaskRecord( mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mNewTaskInfo ! =null? mNewTaskInfo : mStartActivity.info, mNewTaskIntent ! =null? mNewTaskIntent : mIntent, mVoiceSession, mVoiceInteractor, ! mLaunchTaskBehind/* toTop */, mStartActivity, mSourceRecord,
mOptions);
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
updateBounds(mStartActivity.getTask(), mLaunchParams.mBounds);
} else {
/ / with old stack
addOrReparentStartingActivity(mReuseTask, "setTaskFromReuseOrCreateNewTask"); }... . .Copy the code
2.6 Summary
We can see that FLAG_ACTIVITY_NEW_TASK is required if we need to create a new stack or put the Activity we want to start on a stack that already exists.
Start mode flow chart
Let me summarize the description of part 2 with a diagram.
4. To summarize
Let’s start with a few important conclusions:
-
FLAG_ACTIVITY_NEW_TASK is required to create or reuse stacks. Add FLAG_ACTIVITY_NEW_TASK SingleTask, SingleInstance for mLaunchFlags automatically. That is, they all have the possibility of not using the current stack. SingleInstance is easy to understand, SingleTask needs attention, I will explain SingleTask in detail, don’t worry about it.
-
What are the necessary and sufficient conditions for creating or reusing an existing stack?
FLAG_ACTIVITY_NEW_TASK + taskAffinity(taskAffinity must be different from the current display stack rootAffinity, taskAffinity defaults to the package name).
FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_MULTIPLE_TASK This will definitely create a new stack
-
FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP + (taskAffinity == the application package name). What does FLAG_ACTIVITY_CLEAR_TOP do? For example, the Activity we want to start in SingleTask mode is already in the stack and not at the top of the stack, which will push all previous activities out of the stack.
-
SingleInstance is special in that first Activity in SingleInstance mode is FLAG_ACTIVITY_NEW_TASK, just like SingleTask. One of the special features is that if the Activity is started, it will traverse to find the same Activity, and the search process is different. Instead of SingleTask looking for the “right” stack (2.3 Most suitable reusable stack), taskAffinity looks for it. The second reason is that a SingleInstance stack can only hold one Activity. The reason for this is that when we find a suitable stack based on taskAffinity, if we find a SingleInstance stack, we will ignore it.
Answer the questions
Now that we’re done, let’s go back and think about some of the questions we raised at the beginning.
-
If there are two applications A and B, there are two activities in A, A1 and A2, and one Activity B1 in B. These three activities are in SingleTask mode. The startup sequence is A1 -> B1 -> A2, and then click the back button to return to the interface A1. Since the SingleTask mode itself contains FLAG_ACTIVITY_NEW_TASK, taskAffinity will be placed in the “appropriate” stack (2.3 Most appropriate reusable stack) since taskAffinity is also different from the stack currently displayed (taskAffinity defaults to the package name).
-
What if A2 is Standard + FlAG_ACTIVITY_NEW_TASK? It’s going to go back to A1, because A2 and A1 are on the same stack, similar to the first case.
-
What does FLAG_ACTIVITY_NEW_TASK do? What is taskAffinity? This problem can be seen in the summary above.
The last
There are a lot of questions about startup modes, and here are some common ones.
We know that an Activity in SingleInstance mode cannot be used as an intermediate Activity in an application. For example, in A1, A2 and A3, A2 is SingleInstance mode, A1 and A2 are Standard mode. If the boot order is A1 -> A2 -> A3, and then click the Back key, it will return to A1. If all three activities are in Standard mode, how do you want to achieve the same effect?
Compares the types of articles, I come up to Standard, this article is not SingleTask, SingleInstance, the basic concept of SingleTop mode and use, but more source sex research, make a proposal to each mode in this code to walk again, You can essentially look at the difference between priming patterns and have a deeper understanding of the concept of priming patterns.