Activity有四種launchMode,android官方文檔的介紹如下:
啟動模式 | 多個實例? | 注釋 |
---|---|---|
“standard” | 是 | 默認值。系統始終會在目標任務中創建新的 Activity 實例并向其傳送 Intent。 |
“singleTop” | 有條件 | 如果目標任務的頂部已存在一個 Activity 實例,則系統會通過調用該實例的 onNewIntent() 方法向其傳送 Intent,而不是創建新的 Activity 實例。 |
“singleTask” | 否 | 系統在新任務的根位置創建 Activity 并向其傳送 Intent。 不過,如果已存在一個 Activity 實例,則系統會通過調用該實例的 onNewIntent() 方法向其傳送 Intent,而不是創建新的 Activity 實例。 |
“singleInstance” | 否 | 與“singleTask"”相同,只是系統不會將任何其他 Activity 啟動到包含實例的任務中。 該 Activity 始終是其任務唯一僅有的成員。 |
測試demo
我們通過兩個Activity之間的跳轉和任務棧打印來理解launchMode的作用。
這兩個Activity使用同一個布局, TextView用來打印任務棧的id和activity實例,然后是兩個按鈕,分別用來啟動兩個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());
}
}
因為兩個activity的代碼很相似,所以我把它們放到了靜態類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);
}
}
查看任務棧
我們可以在adb shell中使用dumpsys activity可以看到任務棧。這個命令的打印會比較多,但是有兩個部分是比較重要的。
一個是Recent tasks,這里可以看到各個任務棧的總體信息,如我們的demo在第0個任務棧,它的Task id 是483,包名是linjw.demo.launchmodedem,棧里面有6個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這個Activity是從桌面點擊應用圖標進入的,所以它的Intent帶android.intent.category.LAUNCHER這個category和FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY(0x00100000)、 FLAG_RECEIVER_FOREGROUND(0x10000000)這兩個Flag,而其他的Activity都是我們通過點擊按鈕叫起的,我們的Intent里面沒有帶任何的category和Flag,所以只有cmp標識是哪個Activity。
我們可以通過Intent#setFlags(int) 設置Flag、Intent#addCategory(String) 添加category
有時候可以通過這個命令查看Activity的啟動模式結合各種launchMode的作用來定位一些bug
standard
standard是默認的啟動模式,在沒有配置android:launchMode的時候就會默認用這種啟動模式,當然也可以顯示指定為standard。
它的特點是每次都會啟動一個新的Activity實例,我們連續點擊第一個按鈕兩次然后再連續點擊兩次返回鍵,截圖如下:
任務棧狀態圖如下:
singleTop
當launchMode是singleTop的時候,如果task棧的棧頂Activity和將要啟動的Activity是同一個Activity的話,就不會再啟動第二個Activity。我們將FirstActivity設為singleTop,在啟動demo之后無論按第一個按鈕多少次,任務棧里面都只會有一個FirstActivity。
任務棧狀態圖如下:
但是當任務棧的棧頂Activity和將要啟動的Activity不是同一個Activity的時候,就會啟動新的Activity,并將它壓入棧頂而不管棧里面還有沒有這個Activity:
任務棧狀態圖如下:
singleTask
我們將FirstActivity和SecondActivity的launchMode都設置為singleTask,啟動demo之后先按“GOTO SECOND ACTIVITY”再按“GOTO FIRST ACTIVITY”,截圖如下:
我們可以看到在SecondActivity中啟動FirstActivity,結果就返回了第一個Activity。如果這個時候再按返回鍵就會推出應用。
singleTask的作用就是在任務棧中尋找將要啟動的Activity,如果找到的話就將它上面的Activity都彈出棧,直到它成為棧頂。
任務棧狀態圖如下:
singleInstance
官方文檔的介紹是:
與“singleTask"”相同,只是系統不會將任何其他 Activity 啟動到包含實例的任務中。 該 Activity 始終是其任務唯一僅有的成員。
就是說系統會為singleInstance Activity單獨創建一個任務棧,這個任務棧里是這個Activity獨占的,不會再壓入其他的Activity。而且它是系統唯一的,當singleInstance Activity已經存在于系統的某一任務棧中,就會直接跳到那個任務棧的Activity中,而不會新啟動一個Activity。
我們將FirstActivity設為standard, SecondActivity設為singleInstance。啟動demo之后先按“GOTO SECOND ACTIVITY”再按“GOTO FIRST ACTIVITY”。然后再一直按返回鍵到退出應用。截圖如下:
任務棧狀態圖如下:
啟動應用之后先點擊“GOTO SECOND ACTIVITY”,這個時候系統會新建一個任務棧(Task 20)來放SecondActivity
在SecondActivity中再啟動FirstActivity,因為Task 20這個任務棧是SecondActivity獨占的。所以不會在這個任務棧壓入其他Activity,而會回到原來的任務棧上(Task 19)。又因為FirstActivity的launchMode是standard,所以不管原來的棧里面有沒有FirstActivity,都會壓入一個新的FirstActivity。
這個時候再按返回鍵就不是回到SecondActivity了,因為它在其他的任務棧里面,要先將當前任務棧清空。
這個時候按返回鍵會將當前的Activity彈出棧,于是就跳到了一開始的FirstActivity。之后再按返回鍵,因為Task 19這個任務棧空了,就會去到SecondActivity的棧,于是就去到了SecondActivity。最后再按返回鍵就會退出應用了。
要再次提醒需注意的是singleInstance的Activity是系統唯一的,也就是說你在demo這里啟動了這個SecondActivity的SecondActivity,然后按home鍵回到桌面去啟動其他應用,從其他應用再啟動一個SecondActivity也是去到原來的那個SecondActivity