??本文試圖說明 Activity launchMode 原理,各種常用的 Intent Flag 原理 读第,以及l(fā)aunchMode 和 Intent Flag 的區(qū)別胶哲,相似點等昧诱;以及有可能的使用場景(源碼基于 7.0 分析)晓淀。
SingleTask VS Intent.FLAG_NEW_TASK
??Activity 中有四大啟動模式,較常用的一種為SingleTask官方注釋如下:
"singleTask" can only begin a task. They are always at the root of
the activity stack. Moreover, the device can hold only one instance of
the activity at a time — only one such task.
??大致翻譯如下:如果一個Activity設置為SingleTask盏档,那么它必須在當前棧的底部凶掰,并且在當前棧中,只能有一個該Activity的實例蜈亩。
??上述說法其實是錯誤的懦窘,一個被SingleTask標識的Activity是可以不在棧底的。并且在一個棧中稚配,只能有一個該Activity實例畅涂。那么 SingleTask 的原理是什么呢。
??不同應用的Activity以及Activity的跳轉(zhuǎn)是由AMS來完成的,當啟動一個Activity時糕非,最后都是由 AMS 來完成嘿辟,最后會調(diào)用到 ActivityStarter的startActivityUnchecked()方法,該方法代碼如下:
// Note: This method should only be called from {@link startActivity}.
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity) {
// 1.1 解析 LaunchMode , 設置初始化狀態(tài)
setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
voiceInteractor);
// 1.2 實現(xiàn) LaunchMode 到 LaunchFlag 轉(zhuǎn)換操作
computeLaunchingTaskFlags();
...
// 1.3 此處為關鍵點臊岸,尋找可重用的 ActivityRecord實例
ActivityRecord reusedActivity = getReusableIntentActivity();
...
// 1.4 如果 reusedActivity 不為 null ,走相關邏輯
if(reusedActivity != null){
//后續(xù)具體分析..
}
...
}
??應用在真正啟動一個 Activity 之前,會先走上述1.1~1.3的初始化過程尊流,下面分別來解釋一下 1.1 ~1.3具體做了哪些工作帅戒。
1.1:setInitialState() 初始化工作
在 setInitialState() 過程中,會首先做一些初始化的操作崖技,如記錄該 ActivityRecord 的啟動模式逻住,activityResult 校驗等等,代碼如下:
private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask,
boolean doResume, int startFlags, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) {
reset();
mStartActivity = r;
mIntent = r.intent;
mOptions = options;
mCallingUid = r.launchedFromUid;
mSourceRecord = sourceRecord;
mVoiceSession = voiceSession;
mVoiceInteractor = voiceInteractor;
mLaunchBounds = getOverrideBounds(r, options, inTask);
// 常見的啟動模式的標識迎献,賦值給成員變量
// 標識將要啟動的 Activity 的 LaunchMode
mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
mLaunchSingleInstance = r.launchMode == LAUNCH_SINGLE_INSTANCE;
mLaunchSingleTask = r.launchMode == LAUNCH_SINGLE_TASK;
mLaunchFlags = adjustLaunchFlagsToDocumentMode(
r, mLaunchSingleInstance, mLaunchSingleTask, mIntent.getFlags());
mLaunchTaskBehind = r.mLaunchTaskBehind
&& !mLaunchSingleTask && !mLaunchSingleInstance
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
// 此處也是一個重要方法鄙信,與 startActivityForResult() 密切相關
sendNewTaskResultRequestIfNeeded();
...
}
??從上述初始化操作可以看出,將目標Activity的Launch Mode 賦值給相關的成員變量忿晕,并且在之后有一步 sendNewTaskResultRequestIfNeeded() 的操作装诡,那么這個操作是干什么的呢?該方法代碼如下:
private void sendNewTaskResultRequestIfNeeded() {
if (mStartActivity.resultTo != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0
&& mStartActivity.resultTo.task.stack != null) {
// For whatever reason this activity is being launched into a new task...
// yet the caller has requested a result back. Well, that is pretty messed up,
// so instead immediately send back a cancel and let the new task continue launched
// as normal without a dependency on its originator.
Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
mStartActivity.resultTo.task.stack.sendActivityResultLocked(-1, mStartActivity.resultTo,
mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, null);
mStartActivity.resultTo = null;
}
}
??從該方法注釋可以看出践盼,如果以startActivityForResult() 方式啟動(RequestCode > 0),并且Intent Flag 有設置 FLAG_ACTIVITY_NEW_TASK 標識鸦采,那么當前 Activity 會立即收到 CANCEL 回調(diào)。
??上述問題也是一個比較常見的問題咕幻,從代碼可以看出渔伯,如果想要正確的收到 onActivityResult() 回調(diào),那么在啟動 Activity 的 Intent 中肄程,一定不能加 FLAG_ACTIVITY_NEW_TASK 標識锣吼。
??分析完上述 1.1 之后选浑,接著來看 1.2 computeLaunchingTaskFlags() 具體做了什么事情。
1.2 : computeLaunchingTaskFlags()
代碼如下:
private void computeLaunchingTaskFlags() {
// If the caller is not coming from another activity, but has given us an explicit task into
// which they would like us to launch the new activity, then let's see about doing that.
// 此處不在本文的分析范圍內(nèi)玄叠,如果用戶是通過 Application Context 或者
// Service 啟動的 目標 Activity 古徒,并且有指定要宿主到哪個 task
// 該判斷條件才會成立。
if (mSourceRecord == null && mInTask != null && mInTask.stack != null) {
final Intent baseIntent = mInTask.getBaseIntent();
final ActivityRecord root = mInTask.getRootActivity();
if (baseIntent == null) {
ActivityOptions.abort(mOptions);
throw new IllegalArgumentException("Launching into task without base intent: "
+ mInTask);
}
// If this task is empty, then we are adding the first activity -- it
// determines the root, and must be launching as a NEW_TASK.
if (mLaunchSingleInstance || mLaunchSingleTask) {
if (!baseIntent.getComponent().equals(mStartActivity.intent.getComponent())) {
ActivityOptions.abort(mOptions);
throw new IllegalArgumentException("Trying to launch singleInstance/Task "
+ mStartActivity + " into different task " + mInTask);
}
if (root != null) {
ActivityOptions.abort(mOptions);
throw new IllegalArgumentException("Caller with mInTask " + mInTask
+ " has root " + root + " but target is singleInstance/Task");
}
}
// If task is empty, then adopt the interesting intent launch flags in to the
// activity being started.
if (root == null) {
final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK
| FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_RETAIN_IN_RECENTS;
mLaunchFlags = (mLaunchFlags & ~flagsOfInterest)
| (baseIntent.getFlags() & flagsOfInterest);
mIntent.setFlags(mLaunchFlags);
mInTask.setIntent(mStartActivity);
mAddingToTask = true;
// If the task is not empty and the caller is asking to start it as the root of
// a new task, then we don't actually want to start this on the task. We will
// bring the task to the front, and possibly give it a new intent.
} else if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
mAddingToTask = false;
} else {
mAddingToTask = true;
}
mReuseTask = mInTask;
} else {
mInTask = null;
// Launch ResolverActivity in the source task, so that it stays in the task bounds
// when in freeform workspace.
// Also put noDisplay activities in the source task. These by itself can be placed
// in any task/stack, however it could launch other activities like ResolverActivity,
// and we want those to stay in the original task.
if ((mStartActivity.isResolverActivity() || mStartActivity.noDisplay) && mSourceRecord != null
&& mSourceRecord.isFreeform()) {
mAddingToTask = true;
}
}
// 重點分析此處 读恃, 如果啟動目標 Activity 為 Activity Context ,那么會為 mSourceRecord
// 賦值,如果是從非 Activity 啟動隧膘,比如 Application Context 等,那么mSourceRecord
// 為 null 寺惫。
if (mInTask == null) {
if (mSourceRecord == null) {
// This activity is not being started from another... in this
// case we -always- start a new task.
// 如果是從非 Activity 入口過來疹吃,強制加上 FLAG_NEW_TASK 標識。
if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
"Intent.FLAG_ACTIVITY_NEW_TASK for: " + mIntent);
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
} else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
// The original activity who is starting us is running as a single
// instance... this new activity it is starting must go on its
// own task.
// 如果 當前 Activity Launch Mode 為 singleInstance西雀,代表不允許目標
// Activity 與宿主 Activity 棧實例共用一個 task 棧萨驶,所以也會強制加上
// FLAG_ACTIVITY_NEW_TASK 標識。
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
} else if (mLaunchSingleInstance || mLaunchSingleTask) {
// The activity being started is a single instance... it always
// gets launched into its own task.
// 如果目標 Activity 的 LaunchMode 為 singleTask 或者 singleInstance
// 那么也強制加上 FLAG_ACTIVITY_NEW_TASK 標識艇肴。
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
}
}
??該方法的說明已經(jīng)在上述代碼中注釋的比較清楚腔呜,總結一些,可以羅列為以下幾點:
- 如果是從非 Activity 入口過來豆挽,比如 Application Context 育谬、Service 等 , 強制加上 FLAG_ACTIVITY_NEW_TASK 標識券盅。
- 如果目標 Activity 啟動模式為 SingleTask 或者 SingleInstance 帮哈,那么也強制加上 FLAG_ACTIVITY_NEW_TASK 標識。
- 如果源 Activity 啟動模式為 SingleInstance , 那么肯定不允許目標 Activity 與源 Activity 公用一個 Task 棧锰镀,所以也強制加上 FLAG_ACTIVITY_NEW_TASK 標識娘侍。
1.3 :尋找可重用的 ActivityRecord 實例
getReusableIntentActivity() 方法代碼如下:
/**
* Decide whether the new activity should be inserted into an existing task. Returns null
* if not or an ActivityRecord with the task into which the new activity should be added.
*/
private ActivityRecord getReusableIntentActivity() {
// We may want to try to place the new activity in to an existing task. We always
// do this if the target activity is singleTask or singleInstance; we will also do
// this if NEW_TASK has been requested, and there is not an additional qualifier telling
// us to still place it in a new task: multi task, always doc mode, or being asked to
// launch this as a new task behind the current one.
boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
(mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| mLaunchSingleInstance || mLaunchSingleTask;
// If bring to front is requested, and no result is requested and we have not been given
// an explicit task to launch in to, and we can find a task that was started with this
// same component, then instead of launching bring that one to the front.
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) {
// There can be one and only one instance of single instance activity in the
// history, and it is always in its own unique task, so we do a special search.
intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info, false);
} else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
// For the launch adjacent case we only want to put the activity in an existing
// task if the activity already exists in the history.
intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
!mLaunchSingleTask);
} else {
// Otherwise find the best task to put the activity in.
intentActivity = mSupervisor.findTaskLocked(mStartActivity);
}
}
return intentActivity;
}
??從上述代碼注釋可以看出,該方法主要作用為判斷目標 Activity 是否要插入到一個已經(jīng)存在的棧中泳炉,主要判斷依據(jù)羅列如下:
- 對于 LaunchMode 為 singleTask 或 singleInstance 的 Activity憾筏,強制嘗試去尋找是否已經(jīng)存在一個已經(jīng)存在的 Task。
- 對于 含有 FLAG_ACTIVITY_NEW_TASK 標識的 Intent花鹅,也強制嘗試去尋找氧腰。
- 若目標 Activity LaunchMode 為 singleInstance ,強制去找是否已經(jīng)存在相同的 ActivityRecord 實例刨肃。
- 若LaunchMode 不為 singleInstance古拴,則強制去尋找已經(jīng)存在的 Task 實例,特別說明的是真友,此處尋找是否存在相同的 task 實例是通過判斷 taskAffinity 來完成的黄痪,具體代碼邏輯在 mSupervisor.findTaskLocked(mStartActivity) 方法中,此處不再詳細說明盔然。
??通過上述方法可以看出桅打,如果一個 Activity 的 LaunchMode 被標識為 singleInstance , 那么它在 AMS 中永遠只存在唯一的一份實例是嗜。對于 singleTask 或 FLAG_ACTIVITY_NEW_TASK , 都嘗試先根據(jù)該 Activity 對應的 taskAffinity 去找是否已經(jīng)存在相關的棧,如果存在挺尾,則將目標 Activity 放入該棧中鹅搪。
??從上述1.1 ~ 1.3 的分析,發(fā)現(xiàn) launchMode 為 singleTask 與 Intent FLAG_ACTIVITY_NEW_TASK 并沒有什么區(qū)別潦嘶,singleTask 在 1.2 computeLaunchingTaskFlags() 方法中也會強制加上 FLAG_ACTIVITY_NEW_TASK 標識涩嚣,那么兩者的區(qū)別到底是什么呢?
1.4 : reusedActivity 不為 null 處理
從1.3 處分析可知掂僵,對于 launchMode 為 singleTask 或者 Intent Flag 為 FLAG_ACTIVITY_NEW_TASK 的標識航厚,都會首先嘗試去找一個已經(jīng)存在的棧,此處如果可以找到锰蓬,則將棧中的一個 ActivityRecord 實例賦值給 reusedActivity 幔睬,然后繼續(xù)走接下來的流程,代碼如下:
if (reusedActivity != null) {
...
// 重點分析此處方法
setTaskFromIntentActivity(reusedActivity);
if (!mAddingToTask && mReuseTask == null) {
// We didn't do anything... but it was needed (a.k.a., client don't use that
// intent!) And for paranoia, make sure we have correctly resumed the top activity.
resumeTargetStackIfNeeded();
if (outActivity != null && outActivity.length > 0) {
outActivity[0] = reusedActivity;
}
return START_TASK_TO_FRONT;
}
}
??重點分析 setTaskFromIntentActivity(reusedActivity) 方法芹扭,其中 reusedActivity 代表 1.3 找到的 ActivityRecord 實例麻顶, 該方法代碼如下:
private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
// The caller has requested to completely replace any existing task with its new
// activity. Well that should not be too hard...
// 如果 Intent flag 同時設置有 FLAG_ACTIVITY_CLEAR_TASK 與 FLAG_ACTIVITY_NEW_TASK
// 那么會把當前的棧清空,重新開啟一個棧
mReuseTask = intentActivity.task;
// 清空棧操作
mReuseTask.performClearTaskLocked();
mReuseTask.setIntent(mStartActivity);
// When we clear the task - focus will be adjusted, which will bring another task
// to top before we launch the activity we need. This will temporary swap their
// mTaskToReturnTo values and we don't want to overwrite them accidentally.
mMovedOtherTask = true;
} else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| mLaunchSingleInstance || mLaunchSingleTask) {
// 如果目標 Activity launchMode 為 singleTask或
// 者singleInstance , 或者 Intent flag 含有 FLAG_CLEAR_TOP 標識 舱卡,那么首先
// 去清除 棧里面的元素辅肾,直到找到可重用的 ActivityRecord 為止。
// 此處是一個關鍵點
ActivityRecord top = intentActivity.task.performClearTaskLocked(mStartActivity,
mLaunchFlags);
if (top == null) {
// A special case: we need to start the activity because it is not currently
// running, and the caller has asked to clear the current task to have this
// activity at the top.
mAddingToTask = true;
// Now pretend like this activity is being started by the top of its task, so it
// is put in the right place.
mSourceRecord = intentActivity;
final TaskRecord task = mSourceRecord.task;
if (task != null && task.stack == null) {
// Target stack got cleared when we all activities were removed above.
// Go ahead and reset it.
mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
null /* bounds */, mLaunchFlags, mOptions);
mTargetStack.addTask(task,
!mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
}
}
} else if (mStartActivity.realActivity.equals(intentActivity.task.realActivity)) {
// In this case the top activity on the task is the same as the one being launched,
// so we take that as a request to bring the task to the foreground. If the top
// activity in the task is the root activity, deliver this new intent to it if it
// desires.
if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
&& intentActivity.realActivity.equals(mStartActivity.realActivity)) {
ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity,
intentActivity.task);
if (intentActivity.frontOfTask) {
intentActivity.task.setIntent(mStartActivity);
}
intentActivity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
mStartActivity.launchedFromPackage);
} else if (!intentActivity.task.isSameIntentFilter(mStartActivity)) {
// In this case we are launching the root activity of the task, but with a
// different intent. We should start a new instance on top.
mAddingToTask = true;
mSourceRecord = intentActivity;
}
} else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
// In this case an activity is being launched in to an existing task, without
// resetting that task. This is typically the situation of launching an activity
// from a notification or shortcut. We want to place the new activity on top of the
// current task.
mAddingToTask = true;
mSourceRecord = intentActivity;
} else if (!intentActivity.task.rootWasReset) {
// In this case we are launching into an existing task that has not yet been started
// from its front door. The current task has been brought to the front. Ideally,
// we'd probably like to place this new task at the bottom of its stack, but that's
// a little hard to do with the current organization of the code so for now we'll
// just drop it.
intentActivity.task.setIntent(mStartActivity);
}
}
從上述代碼的注釋轮锥,可以很清楚的得到以下結論:
- 如果 Intent flag 同時設置有 FLAG_ACTIVITY_CLEAR_TASK 與 FLAG_ACTIVITY_NEW_TASK 那么會把當前的棧清空矫钓。目標Activity 啟動的時候,與棧內(nèi)之前已經(jīng)存在的 Activity 不是一個實例舍杜。
- 如果目標 Activity launchMode 為 singleTask或者 singleInstance ,那么首先清除棧里面的元素新娜,直到棧頂元素為將要啟動的目標 ActivityRecord 實例為止。
- 若Intent Flag 中包含 FLAG_ACTIVITY_CLEAR_TOP 既绩, 則也執(zhí)行清棧操作概龄,但是會把已經(jīng)存在的 ActivityRecord 首先 finish掉,然后重新啟動目標 ActivityRecord 實例饲握。