面試的時候,面試官常常同你隨便侃侃Activity的啓動模式,但Activity啓動牽扯的知識點其實不少,並不是能單單用四個啓動模式就能歸納的,默認的啓動模式的表現會隨着Intent Flag的設置而改變,所以侃Activity啓動模式大多走流程裝逼,最多結合項目遇到的問題,隨便刁難一下面試者,並不太容易把控,也許最後,面試官跟面試者的答案都是錯了,好比在Service中必須經過設置FLAG_ACTIVITY_NEW_TASK才能啓動Activity,這個時候啓動Activit會有什麼樣的表現呢?就這一個問題,答案就要分好幾個場景:面試
不一樣場景,所表現的行爲都會有所不一樣,再好比singleInstance屬性,若是設置了,你們都知道只有一個實例,未來再啓動會複用,可是若是使用Intent.FLAG_ACTIVITY_CLEAR_TASK來啓動,仍然會重建,並不是徹底遵照singleInstance的說明,還有不一樣Flag在疊加使用時候也會有不一樣的表現,單一而論Activity啓動模式實際上是很難的。本文也僅僅是涉及部分啓動模式及Flag,更多組合跟場景要本身看源碼或者實驗來解決了。bash
本文假定你們對於Activity的Task棧已經有初步瞭解,首先,看一下Activity常見的四種啓動模式及大衆理解,這也是面試時最長問的:app
須要說明的是:上面的場景僅僅適用於Activity啓動Activity,而且採用的都是默認Intent,沒有額外添加任何Flag,不然表現就可能跟上面的徹底不一致,尤爲要注意的是FLAG_ACTIVITY_NEW_TASK的使用,後面從源碼中看,依靠FLAG_ACTIVITY_NEW_TASK其實能夠分爲兩派。ide
從源碼來看,Intent.FLAG_ACTIVITY_NEW_TASK是啓動模式中最關鍵的一個Flag,依據該Flag啓動模式能夠分紅兩類,設置了該屬性的與未設置該屬性的,對於非Activity啓動的Activity(好比Service中啓動的Activity)須要顯示的設置Intent.FLAG_ACTIVITY_NEW_TASK,而singleTask及singleInstance在AMS中被預處理後,隱形的設置了Intent.FLAG_ACTIVITY_NEW_TASK,而啓動模式是standard及singletTop的Activity不會被設置Intent.FLAG_ACTIVITY_NEW_TASK,除非經過顯示的intent setFlag進行設置。源碼分析
FLAG_ACTIVITY_NEW_TASK這個屬性更多的關注點是在Task,大多數狀況下,須要將Activity引入到本身taskAffinity的Task中,Intent.FLAG_ACTIVITY_NEW_TASK的初衷是在Activity目標taskAffinity的Task中啓動,非Activity啓動Activity都必須添加Intent.FLAG_ACTIVITY_NEW_TASK才行,以Service啓動的Activity爲例:ui
Intent intent = new Intent(BackGroundService.this, A.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); 複製代碼
這種狀況頗有意思,若是目標Activity實例或者Task不存在,則必定會新建Activity,並將目標Task移動到前臺,可是若是Activity存在,卻並不必定複用,也不必定可見。這裏假定A是standard的Activity,若是已經有一個A實例,而且所在的堆棧的taskAffinity跟A的taskAffinity一致,這個時候要看這個task的根Activity是否是A,若是是A,還要看A的intent是否是跟當前的啓動的intent相等,若是都知足,只要將task可見便可。不然,就須要新建A,並根據A的task棧的存在狀況而選擇直接入棧仍是新建棧。可是,若是Intent想要的啓動的Activity的目標堆棧存在,那就將整個堆棧往前遷移,若是位於頂部的Task棧正好是目標Activity的Task棧,那就不作任何處理,連onNewIntent都不會回調,怎麼判斷目標的Activity的Task棧同找到的棧一致呢?若是找不到目標Task天然會啓動Task,若是目標task棧根Activit的intent同新將要啓動的Activit相同,就不啓動新Activity,不然啓動Activity。this
If set in an Intent passed to Context.startActivity(), this flag will cause any existing task that would be associated with the activity to be cleared before the activity is started. That is, the activity becomes the new root of an otherwise empty task, and any old activities are finished. This can only be used in conjunction with FLAG_ACTIVITY_NEW_TASK.spa
這個屬性必須同FLAG_ACTIVITY_NEW_TASK配合使用,若是設置了FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK,若是目標task已經存在,將清空已存在的目標Task,不然,新建一個Task棧,以後,新建一個Activity做爲根Activity。Intent.FLAG_ACTIVITY_CLEAR_TASK的優先級最高,基本能夠無視全部的配置,包括啓動模式及Intent Flag,哪怕是singleInstance也會被finish,並重建。3d
若是沒有使用FLAG_ACTIVITY_NEW_TASK,目標是當前Task棧,根據不一樣的組合會產生不一樣的效果,若是單獨使用Intent.FLAG_ACTIVITY_CLEAR_TOP,而且沒有設置特殊的launchmode,那麼,Google官方的示例是:若是ABCD Task中的D採用Intent.FLAG_ACTIVITY_CLEAR_TOP喚起B,這個時候首先會將CD出棧,可是至於B是否會重建,要視狀況而定,若是沒有設置FLAG_ACTIVITY_SINGLE_TOP,則會將B finish掉,以後建立新的入棧。若是同一個棧中原來有rest
若是沒有則新建,不會去另外一個棧中尋找。
若是同時設置了FLAG_ACTIVITY_SINGLE_TOP,在當前棧已有的狀況下就不會重建,而是直接回調B的onNewIntent(),
官方解釋以下:
For example, consider a task consisting of the activities: A, B, C, D. If D calls startActivity() with an Intent that resolves to the component of activity B, then C and D will be finished and B receive the given Intent, resulting in the stack now being: A, B。
The currently running instance of activity B in the above example will either receive the new intent you are starting here in its onNewIntent() method, or be itself finished and restarted with the new intent. If it has declared its launch mode to be "multiple" (the default) and you have not set FLAG_ACTIVITY_SINGLE_TOP in the same intent, then it will be finished and re-created; for all other launch modes or if FLAG_ACTIVITY_SINGLE_TOP is set then this Intent will be delivered to the current instance's onNewIntent().
若是同時使用了FLAG_ACTIVITY_NEW_TASK ,這個時候,目標是Activity本身所屬的Task棧,若是在本身的Task中能找到一個Activity實例,則將其上面的及自身清理掉,以後重建。
若是同時在加上FLAG_ACTIVITY_SINGLE_TOP,會更特殊一些,若是topActivity不是目標Activity,就會去目標Task中去找,並喚起
若是topActivity是目標Activity,就直接回調topActivity的onNewIntent,不管topActivity是否是在目標Task中
Intent.FLAG_ACTIVITY_SINGLE_TOP多用來作輔助做用,跟launchmode中的singleTop做用同樣,在Task棧頂有的話,就不新建,棧頂沒有的話,就新建,這裏的Task多是目標棧,也多是當前Task棧,配合FLAG_ACTIVITY_NEW_TASK及FLAG_ACTIVITY_CLEAR_TOP都會有頗有意思的效果。
如今咱們看一下源碼,來分析下原理:從源碼更能看出FLAG_ACTIVITY_NEW_TASK重要性,這裏只分析部分場景,否則太多了: 下面的是4.3的代碼,比較簡單些:
final int startActivityUncheckedLocked(ActivityRecord r,
ActivityRecord sourceRecord, int startFlags, boolean doResume,
Bundle options) {
final Intent intent = r.intent;
final int callingUid = r.launchedFromUid;
...
<!--關鍵點1:直接獲取launchFlags-->
int launchFlags = intent.getFlags();
...
<!--關鍵點2:預處理一些特殊的 launchmode,主要是設置Intent.FLAG_ACTIVITY_NEW_TASK-->
// 若是sourceRecord ==null 說明不是從activity啓動的,從服務開啓就必定要start a new task
if (sourceRecord == null) {
if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
} else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
} else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
...
<!--關鍵點3 對於Intent.FLAG_ACTIVITY_NEW_TASK 對sourceActivity直接返回cancel-->
if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
sendActivityResultLocked(-1,
r.resultTo, r.resultWho, r.requestCode,
Activity.RESULT_CANCELED, null);
r.resultTo = null;
}
boolean addingToTask = false;
boolean movedHome = false;
TaskRecord reuseTask = null;
<!--關鍵點4 對於Intent.FLAG_ACTIVITY_NEW_TASK分支的處理-->
if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
(launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
if (r.resultTo == null) {
<!--關鍵點5找到目標Task棧的棧頂元素 ,可是taskTop不必定是目標Activity-->
ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
?findTaskLocked(intent, r.info)
: findActivityLocked(intent, r.info);
<!--若是目標棧存在-->
if (taskTop != null) {
ActivityRecord curTop = topRunningNonDelayedActivityLocked(notTop);
<!--關鍵點6 看找到的Task是否是位於棧頂,若是不是則移動-->
if (curTop != null && curTop.task != taskTop.task) {
r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
boolean callerAtFront = sourceRecord == null
|| curTop.task == sourceRecord.task;
if (callerAtFront) {
movedHome = true;
moveHomeToFrontFromLaunchLocked(launchFlags);
moveTaskToFrontLocked(taskTop.task, r, options);
options = null;
}
}
...
<!--關鍵點6 若是設置了Intent.FLAG_ACTIVITY_CLEAR_TASK,則重置Task,併爲reuseTask賦值,這個屬性只能配合Intent.FLAG_ACTIVITY_NEW_TASK使用-->
if ((launchFlags &
(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK))
== (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) {
reuseTask = taskTop.task;
performClearTaskLocked(taskTop.task.taskId);
reuseTask.setIntent(r.intent, r.info);
<!--關鍵點7 若是設置了Intent.FLAG_ACTIVITY_CLEAR_TOP,則將Task中目標Activity之上的清空,至於本身是否要清空,還要看是否是設置了singltTop-->
} else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
<!--若是能找到一個SingleTop,則不用重建-->
ActivityRecord top = performClearTaskLocked(
taskTop.task.taskId, r, launchFlags);
if (top != null) {
<!--若是本身是rootActivity,則重置intent-->
if (top.frontOfTask) {
top.task.setIntent(r.intent, r.info);
}
top.deliverNewIntentLocked(callingUid, r.intent);
} else {
<!--找不到的話,就新建 ,而且爲sourceRecord賦值-->
addingToTask = true;
sourceRecord = taskTop;
}
} else if (r.realActivity.equals(taskTop.task.realActivity)) {
<!--若是taskTop的Task rootActivity是目標Activity,則額外,若是是Intent.FLAG_ACTIVITY_SINGLE_TOP,並本身位於棧頂 不新建-->
if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP)
&& taskTop.realActivity.equals(r.realActivity)) {
if (taskTop.frontOfTask) {
taskTop.task.setIntent(r.intent, r.info);
}
taskTop.deliverNewIntentLocked(callingUid, r.intent);
<!--若是root intent不相等,則新建Activity,並加入目標棧-->
} else if (!r.intent.filterEquals(taskTop.task.intent)) {
addingToTask = true;
sourceRecord = taskTop;
}
}
...
<!--以上部分主要是對Intent.FLAG_ACTIVITY_NEW_TASK的處理,有些須要直接返回的場景-->
if (!addingToTask && reuseTask == null) {
if (doResume) {
resumeTopActivityLocked(null, options);
} else {
ActivityOptions.abort(options);
}
return ActivityManager.START_TASK_TO_FRONT;
}
}
}
}
if (r.packageName != null) {
ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);
if (top != null && r.resultTo == null) {
if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
if (top.app != null && top.app.thread != null) {
<!--可能不是目標task,可是是Intent.FLAG_ACTIVITY_SINGLE_TOP-->
if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task);
if (doResume) {
resumeTopActivityLocked(null);
}
ActivityOptions.abort(options);
if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
return ActivityManager.START_RETURN_INTENT_TO_CALLER;
}
top.deliverNewIntentLocked(callingUid, r.intent);
return ActivityManager.START_DELIVERED_TO_TOP;
}
}
}
}
}
...
boolean newTask = false;
boolean keepCurTransition = false;
<!--最終判斷是否是要啓動新Task棧-->
if (r.resultTo == null && !addingToTask
&& (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
<!--新建Task 沒找到reuseTask task 而且是launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK-->
if (reuseTask == null) {
mService.mCurTask++;
if (mService.mCurTask <= 0) {
mService.mCurTask = 1;
}
r.setTask(new TaskRecord(mService.mCurTask, r.info, intent), null, true);
} else {
<!--找到Task,可是rootActivity不是目標Activiyt,則須要新建activity-->
r.setTask(reuseTask, reuseTask, true);
}
newTask = true;
if (!movedHome) {
moveHomeToFrontFromLaunchLocked(launchFlags);
}
} else if (sourceRecord != null) {
<!--非服務類啓動的Activity,若是當前棧能找到目標棧的singleTop,則不新建,不然新建-->
if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
ActivityRecord top = performClearTaskLocked(
sourceRecord.task.taskId, r, launchFlags);
keepCurTransition = true;
if (top != null) {
top.deliverNewIntentLocked(callingUid, r.intent);
if (doResume) {
resumeTopActivityLocked(null);
}
ActivityOptions.abort(options);
return ActivityManager.START_DELIVERED_TO_TOP;
}
}
...
r.setTask(sourceRecord.task, sourceRecord.thumbHolder, false);
} else {
final int N = mHistory.size();
ActivityRecord prev =
N > 0 ? mHistory.get(N-1) : null;
r.setTask(prev != null
? prev.task
: new TaskRecord(mService.mCurTask, r.info, intent), null, true);
}
startActivityLocked(r, newTask, doResume, keepCurTransition, options);
return ActivityManager.START_SUCCESS;
}複製代碼
從源碼上說,ContextImpl在前期作了檢查,若是沒添加Intent.FLAG_ACTIVITY_NEW_TASK就拋出異常,
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
...
}複製代碼
爲何要這麼呢?其實直觀很好理解,若是不是在Activity中啓動的,那就能夠看作不是用戶主動的行爲,也就說這個界面可能出如今任何APP之上,若是不用Intent.FLAG_ACTIVITY_NEW_TASK將其限制在本身的Task中,那用戶可能會認爲該Activity是當前可見APP的頁面,這是不合理的。舉個例子:咱們在聽音樂,這個時候若是郵件Service忽然要打開一個Activity,若是不用Intent.FLAG_ACTIVITY_NEW_TASK作限制,那用戶可能認爲這個Activity是屬於音樂APP的,由於用戶點擊返回的時候,可能會回到音樂,而不是郵件(若是郵件以前就有界面)。
以上分析只是針對一個版本的Android,而且只涉及部分Flag,要徹底理解各類組合就更麻煩了,因此所,若是面試官問題Activity啓動模式的話,隨便侃侃還能夠,可是要以此來鄙視你,那你有90%的機率能夠懟回去,怎麼懟?隨便幾個Flag組合一下,問面試官會有什麼結果,保證問死一堆 ^_^;
做者:看書的小蝸牛
原文連接: Android面試官裝逼失敗之:Activity的啓動模式
僅供參考,歡迎指正