Activity有四種launchMode,android官方文檔的介紹如下:
| 啟動模式 | 多個(gè)實(shí)例? | 注釋 |
|---|---|---|
| “standard” | 是 | 默認(rèn)值。系統(tǒng)始終會在目標(biāo)任務(wù)中創(chuàng)建新的 Activity 實(shí)例并向其傳送 Intent。 |
| “singleTop” | 有條件 | 如果目標(biāo)任務(wù)的頂部已存在一個(gè) Activity 實(shí)例,則系統(tǒng)會通過調(diào)用該實(shí)例的 onNewIntent() 方法向其傳送 Intent,而不是創(chuàng)建新的 Activity 實(shí)例。 |
| “singleTask” | 否 | 系統(tǒng)在新任務(wù)的根位置創(chuàng)建 Activity 并向其傳送 Intent。 不過,如果已存在一個(gè) Activity 實(shí)例,則系統(tǒng)會通過調(diào)用該實(shí)例的 onNewIntent() 方法向其傳送 Intent,而不是創(chuàng)建新的 Activity 實(shí)例。 |
| “singleInstance” | 否 | 與“singleTask"”相同,只是系統(tǒng)不會將任何其他 Activity 啟動到包含實(shí)例的任務(wù)中。 該 Activity 始終是其任務(wù)唯一僅有的成員。 |
測試demo
我們通過兩個(gè)Activity之間的跳轉(zhuǎn)和任務(wù)棧打印來理解launchMode的作用。
這兩個(gè)Activity使用同一個(gè)布局, TextView用來打印任務(wù)棧的id和activity實(shí)例,然后是兩個(gè)按鈕,分別用來啟動兩個(gè)activity:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/gotoFirstActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="goto first activity" />
<Button
android:id="@+id/gotoSecondActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="goto second activity" />
</LinearLayout>
Activty代碼如下:
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
Utils.initActivity(this);
}
public void onClick(View view) {
Utils.onClick(this, view.getId());
}
}
public class SecondActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
Utils.initActivity(this);
}
public void onClick(View view) {
Utils.onClick(this, view.getId());
}
}
因?yàn)閮蓚€(gè)activity的代碼很相似,所以我把它們放到了靜態(tài)類Utils中:
public class Utils {
public static void initActivity(Activity activity) {
TextView label = (TextView) activity.findViewById(R.id.label);
label.setText("task : " + activity.getTaskId() + " activity : " + activity);
}
public static void onClick(Context context, int id) {
Intent intent;
if (id == R.id.gotoFirstActivity) {
intent = new Intent(context, FirstActivity.class);
} else {
intent = new Intent(context, SecondActivity.class);
}
context.startActivity(intent);
}
}
查看任務(wù)棧
我們可以在adb shell中使用dumpsys activity可以看到任務(wù)棧。這個(gè)命令的打印會比較多,但是有兩個(gè)部分是比較重要的。
一個(gè)是Recent tasks,這里可以看到各個(gè)任務(wù)棧的總體信息,如我們的demo在第0個(gè)任務(wù)棧,它的Task id 是483,包名是linjw.demo.launchmodedem,棧里面有6個(gè)Activity
ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)
Recent tasks:
* Recent #0: TaskRecord{f69123d #483 A=linjw.demo.launchmodedemo U=0 sz=6}
* Recent #1: TaskRecord{b798a32 #487 A=com.android.systemui U=0 sz=0}
* Recent #2: TaskRecord{f874383 #486 A=com.tencent.mm U=0 sz=1}
* Recent #3: TaskRecord{d7bd900 #479 A=com.meizu.flyme.launcher U=0 sz=1}
* Recent #4: TaskRecord{8f5d797 #482 A=com.tencent.mobileqq U=0 sz=0}
* Recent #5: TaskRecord{fd99539 #481 A=android.task.stk.task U=0 sz=0}
* Recent #6: TaskRecord{7c9951e #480 A=com.android.incallui U=0 sz=1}
* Recent #7: TaskRecord{e32177e #478 A=com.zhihu.android U=0 sz=0}
* Recent #8: TaskRecord{7f094df #472 A=com.meizu.media.reader U=0 sz=0}
在它的下面可以看到里面的具體的activity,并且可以看到叫起它的Intent:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
Stack #1:
Task id #483
TaskRecord{f69123d #483 A=linjw.demo.launchmodedemo U=0 sz=6}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10100000 cmp=linjw.demo.launchmodedemo/.FirstActivity }
Hist #5: ActivityRecord{786c71e u0 linjw.demo.launchmodedemo/.FirstActivity t483}
Intent { cmp=linjw.demo.launchmodedemo/.FirstActivity }
ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
Hist #4: ActivityRecord{6d5da5d u0 linjw.demo.launchmodedemo/.SecondActivity t483}
Intent { cmp=linjw.demo.launchmodedemo/.SecondActivity }
ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
Hist #3: ActivityRecord{90b9f88 u0 linjw.demo.launchmodedemo/.FirstActivity t483}
Intent { cmp=linjw.demo.launchmodedemo/.FirstActivity }
ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
Hist #2: ActivityRecord{1de66e u0 linjw.demo.launchmodedemo/.SecondActivity t483}
Intent { cmp=linjw.demo.launchmodedemo/.SecondActivity }
ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
Hist #1: ActivityRecord{17dd5b1 u0 linjw.demo.launchmodedemo/.FirstActivity t483}
Intent { cmp=linjw.demo.launchmodedemo/.FirstActivity }
ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
Hist #0: ActivityRecord{9f195f8 u0 linjw.demo.launchmodedemo/.FirstActivity t483}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10100000 cmp=linjw.demo.launchmodedemo/.FirstActivity }
ProcessRecord{245852c 7994:linjw.demo.launchmodedemo/u0a61}
...
例如Hist #0這個(gè)Activity是從桌面點(diǎn)擊應(yīng)用圖標(biāo)進(jìn)入的,所以它的Intent帶android.intent.category.LAUNCHER這個(gè)category和FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY(0x00100000)、 FLAG_RECEIVER_FOREGROUND(0x10000000)這兩個(gè)Flag,而其他的Activity都是我們通過點(diǎn)擊按鈕叫起的,我們的Intent里面沒有帶任何的category和Flag,所以只有cmp標(biāo)識是哪個(gè)Activity。
我們可以通過Intent#setFlags(int) 設(shè)置Flag、Intent#addCategory(String) 添加category
有時(shí)候可以通過這個(gè)命令查看Activity的啟動模式結(jié)合各種launchMode的作用來定位一些bug
standard
standard是默認(rèn)的啟動模式,在沒有配置android:launchMode的時(shí)候就會默認(rèn)用這種啟動模式,當(dāng)然也可以顯示指定為standard。
它的特點(diǎn)是每次都會啟動一個(gè)新的Activity實(shí)例,我們連續(xù)點(diǎn)擊第一個(gè)按鈕兩次然后再連續(xù)點(diǎn)擊兩次返回鍵,截圖如下:


任務(wù)棧狀態(tài)圖如下:

singleTop
當(dāng)launchMode是singleTop的時(shí)候,如果task棧的棧頂Activity和將要啟動的Activity是同一個(gè)Activity的話,就不會再啟動第二個(gè)Activity。我們將FirstActivity設(shè)為singleTop,在啟動demo之后無論按第一個(gè)按鈕多少次,任務(wù)棧里面都只會有一個(gè)FirstActivity。

任務(wù)棧狀態(tài)圖如下:

但是當(dāng)任務(wù)棧的棧頂Activity和將要啟動的Activity不是同一個(gè)Activity的時(shí)候,就會啟動新的Activity,并將它壓入棧頂而不管棧里面還有沒有這個(gè)Activity:


任務(wù)棧狀態(tài)圖如下:

singleTask
我們將FirstActivity和SecondActivity的launchMode都設(shè)置為singleTask,啟動demo之后先按“GOTO SECOND ACTIVITY”再按“GOTO FIRST ACTIVITY”,截圖如下:

我們可以看到在SecondActivity中啟動FirstActivity,結(jié)果就返回了第一個(gè)Activity。如果這個(gè)時(shí)候再按返回鍵就會推出應(yīng)用。
singleTask的作用就是在任務(wù)棧中尋找將要啟動的Activity,如果找到的話就將它上面的Activity都彈出棧,直到它成為棧頂。
任務(wù)棧狀態(tài)圖如下:

singleInstance
官方文檔的介紹是:
與“singleTask"”相同,只是系統(tǒng)不會將任何其他 Activity 啟動到包含實(shí)例的任務(wù)中。 該 Activity 始終是其任務(wù)唯一僅有的成員。
就是說系統(tǒng)會為singleInstance Activity單獨(dú)創(chuàng)建一個(gè)任務(wù)棧,這個(gè)任務(wù)棧里是這個(gè)Activity獨(dú)占的,不會再壓入其他的Activity。而且它是系統(tǒng)唯一的,當(dāng)singleInstance Activity已經(jīng)存在于系統(tǒng)的某一任務(wù)棧中,就會直接跳到那個(gè)任務(wù)棧的Activity中,而不會新啟動一個(gè)Activity。
我們將FirstActivity設(shè)為standard, SecondActivity設(shè)為singleInstance。啟動demo之后先按“GOTO SECOND ACTIVITY”再按“GOTO FIRST ACTIVITY”。然后再一直按返回鍵到退出應(yīng)用。截圖如下:


任務(wù)棧狀態(tài)圖如下:

啟動應(yīng)用之后先點(diǎn)擊“GOTO SECOND ACTIVITY”,這個(gè)時(shí)候系統(tǒng)會新建一個(gè)任務(wù)棧(Task 20)來放SecondActivity
在SecondActivity中再啟動FirstActivity,因?yàn)門ask 20這個(gè)任務(wù)棧是SecondActivity獨(dú)占的。所以不會在這個(gè)任務(wù)棧壓入其他Activity,而會回到原來的任務(wù)棧上(Task 19)。又因?yàn)镕irstActivity的launchMode是standard,所以不管原來的棧里面有沒有FirstActivity,都會壓入一個(gè)新的FirstActivity。
這個(gè)時(shí)候再按返回鍵就不是回到SecondActivity了,因?yàn)樗谄渌娜蝿?wù)棧里面,要先將當(dāng)前任務(wù)棧清空。
這個(gè)時(shí)候按返回鍵會將當(dāng)前的Activity彈出棧,于是就跳到了一開始的FirstActivity。之后再按返回鍵,因?yàn)門ask 19這個(gè)任務(wù)??樟?就會去到SecondActivity的棧,于是就去到了SecondActivity。最后再按返回鍵就會退出應(yīng)用了。
要再次提醒需注意的是singleInstance的Activity是系統(tǒng)唯一的,也就是說你在demo這里啟動了這個(gè)SecondActivity的SecondActivity,然后按home鍵回到桌面去啟動其他應(yīng)用,從其他應(yīng)用再啟動一個(gè)SecondActivity也是去到原來的那個(gè)SecondActivity