任務(wù)和返回棧
應(yīng)用通常包含多個(gè) Activity ,每個(gè) Activity 均應(yīng)圍繞用戶可以執(zhí)行的特定操作設(shè)計(jì),并且能夠啟動(dòng)其他 Activity,一個(gè) Activity 可以啟動(dòng)設(shè)備上其他應(yīng)用中的 Activity,即使兩個(gè) Activity 可能來(lái)自不同的應(yīng)用,但是 Android 仍會(huì)將 Activity 保留在相同的任務(wù)中,以維護(hù)這種無(wú)縫的用戶體驗(yàn)。這里所說(shuō)的任務(wù)就是指在執(zhí)行特定作業(yè)時(shí)與用戶交互的一系列 Activity,這些 Activity 按照各自的打開(kāi)順序排列在堆棧(即返回棧)中。返回棧以“后進(jìn)先出”對(duì)象結(jié)構(gòu)運(yùn)行,如下圖

如果要查看 Activity Task棧的情況,可以在命令行用 adb 命令查看
adb shell dumpsys activity activities
執(zhí)行命令會(huì)出現(xiàn)很長(zhǎng)一段詳細(xì)信息 找到 Running activities 即可查看,如下圖

啟動(dòng)模式
在了解了任務(wù)和返回棧后,我們來(lái)說(shuō)說(shuō)啟動(dòng)模式,上圖的堆棧是比較常規(guī)的,如果我們一直啟動(dòng)同一個(gè) Activity 系統(tǒng)會(huì)重復(fù)創(chuàng)建多個(gè)實(shí)例,但這不是我們想要的結(jié)果。這時(shí)候?yàn)榱藵M足我們的需求就需要使用 Android 提供的啟動(dòng)模式來(lái)修改系統(tǒng)的默認(rèn)行為。目前有四種啟動(dòng)模式:standard、singleTop、singleTask 和 singleInstance。在 AndroidManifest.xml 中配置即可,如下:
<activity
android:name="com.will.testdemo.launchmode.A"
android:launchMode="standard">
</activity>
standard 默認(rèn)模式
系統(tǒng)在啟動(dòng) Activity 的任務(wù)中創(chuàng)建 Activity 的新實(shí)例并向其傳送 Intent。Activity 可以多次實(shí)例化,不管這個(gè)實(shí)例是否已經(jīng)存在,而每個(gè)實(shí)例均可屬于不同的任務(wù),并且一個(gè)任務(wù)可以擁有多個(gè)實(shí)例。這種模式的 Activity 被創(chuàng)建時(shí)它的 onCreate、onStart 都會(huì)被調(diào)用。這是一種典型的多實(shí)例實(shí)現(xiàn),一個(gè)任務(wù)棧中可以有多個(gè)實(shí)例,每個(gè)實(shí)例也可以屬于不同的任務(wù)棧。在這種模式下,誰(shuí)啟動(dòng)了這個(gè) Activity,那么這個(gè) Activity 就運(yùn)行在啟動(dòng)它的那個(gè) Activity 所在的棧中。
這里通過(guò)簡(jiǎn)單的代碼來(lái)驗(yàn)證,先實(shí)現(xiàn)方法來(lái)打印 Activity 的生命周期調(diào)用過(guò)程和 Taskid
fun printTaskInfo(activity: Activity, methodName: String) {
log("${activity.localClassName} $methodName taskId = ${activity.taskId}")
}
fun log(message: String, tag: String = "debugLog") {
Log.i(tag, message)
}
/**
* @param T 目標(biāo) Activity
*/
inline fun <reified T : Activity> Context.toActivity() {
startActivity(Intent(this, T::class.java))
}
界面很簡(jiǎn)單就一個(gè)按鈕,就不截圖了,Activity 代碼
class A : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_a)
printTaskInfo(this,"onCreate")
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
printTaskInfo(this,"onNewIntent")
}
override fun onStart() {
super.onStart()
printTaskInfo(this,"onStart")
}
fun click(view: View?) {
toActivity<A>()
}
}
啟動(dòng) A 然后點(diǎn)擊兩下按鈕,日志如下:
01-02 22:07:00.330 28281-28281/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 44
01-02 22:07:00.332 28281-28281/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 44
01-02 22:07:01.580 28281-28281/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 44
01-02 22:07:01.582 28281-28281/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 44
01-02 22:07:02.325 28281-28281/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 44
01-02 22:07:02.327 28281-28281/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 44
使用 adb 命令查看 Activity Task 棧,可以看出每啟動(dòng)一次 A 都會(huì)創(chuàng)建一次實(shí)例,不管這個(gè)實(shí)例是否已經(jīng)存在

singleTop 棧頂復(fù)用模式
在這種模式下,如果當(dāng)前任務(wù)的頂部已存在 Activity 的一個(gè)實(shí)例,則系統(tǒng)會(huì)通過(guò)調(diào)用該實(shí)例的 onNewIntent() 方法向其傳送 Intent,而不是創(chuàng)建 Activity 的新實(shí)例。Activity 可以多次實(shí)例化,而每個(gè)實(shí)例均可屬于不同的任務(wù),并且一個(gè)任務(wù)可以擁有多個(gè)實(shí)例(但前提是位于返回棧頂部的 Activity 并不是 Activity 的現(xiàn)有實(shí)例)。這個(gè) Activity 的 onCreate、onStart 不會(huì)被系統(tǒng)調(diào)用,因?yàn)樗](méi)有發(fā)生改變。

這里我們新建一個(gè) Activity B ,調(diào)用 Activity 流程 :A -> A -> B -> A
<activity
android:name="com.will.testdemo.launchmode.A"
android:launchMode="singleTop">
</activity>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_a)
printTaskInfo(this, "onCreate")
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
printTaskInfo(this, "onNewIntent")
}
override fun onStart() {
super.onStart()
printTaskInfo(this, "onStart")
}
fun click(view: View?) {
when (view?.id) {
R.id.bt_toA -> toActivity<A>()
R.id.bt_toB -> toActivity<B>()
else -> { }
}
}
打印日志:
01-02 22:15:18.399 28530-28530/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 45
01-02 22:15:18.400 28530-28530/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 45
01-02 22:15:21.229 28530-28530/com.will.testdemo I/debugLog: launchmode.A onNewIntent taskId = 45
01-02 22:15:24.927 28530-28530/com.will.testdemo I/debugLog: launchmode.B onCreate taskId = 45
01-02 22:15:24.929 28530-28530/com.will.testdemo I/debugLog: launchmode.B onStart taskId = 45
01-02 22:15:26.449 28530-28530/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 45
01-02 22:15:26.450 28530-28530/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 45
Activity Task 棧

可以看出當(dāng) A 在當(dāng)前棧頂?shù)臅r(shí)候沒(méi)有創(chuàng)建新的實(shí)例,并調(diào)用 onNewIntent 方法,沒(méi)有調(diào)用 onCreate 和 onStart 方法
singleTask 棧內(nèi)復(fù)用模式
這是一種單實(shí)例模式,在這種模式下,只要 Activity 在一個(gè)棧中存在,那么多次啟動(dòng)此 Activity 都不會(huì)重新創(chuàng)建實(shí)例,和 singleTop一樣,系統(tǒng)也會(huì)回調(diào)其 onNewIntent。當(dāng)一個(gè)具有 singleTask 模式的Activity請(qǐng)求啟動(dòng)后,比如 Activity A,系統(tǒng)首先會(huì)尋找是否存在 A 想要的任務(wù)棧,如果不存在,就重新創(chuàng)建一個(gè)任務(wù)棧,然后創(chuàng)建 A 的實(shí)例后把 A 放到棧中。如果存在 A 所需的任務(wù)棧,這時(shí)要看 A 是否在棧中有實(shí)例存在,如果有實(shí)例存在,那么系統(tǒng)就會(huì)把 A 調(diào)到棧頂并調(diào)用它的 onNewIntent 方法,如果實(shí)例不存在,就創(chuàng)建 A 的實(shí)例并把 A 壓入棧中 。

關(guān)于上文中所說(shuō)的想要的任務(wù)棧,指的是 taskAffinity 屬性,手動(dòng)設(shè)置所需的任務(wù)棧,這個(gè)后面會(huì)具體介紹
調(diào)用 Activity 流程 依舊是:A -> A -> B -> A
打印日志:
01-02 22:25:59.608 24498-24498/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 54
01-02 22:25:59.611 24498-24498/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 54
01-02 22:26:02.844 24498-24498/com.will.testdemo I/debugLog: launchmode.A onNewIntent taskId = 54
01-02 22:26:05.753 24498-24498/com.will.testdemo I/debugLog: launchmode.B onCreate taskId = 54
01-02 22:26:05.758 24498-24498/com.will.testdemo I/debugLog: launchmode.B onStart taskId = 54
01-02 22:26:07.040 24498-24498/com.will.testdemo I/debugLog: launchmode.A onNewIntent taskId = 54
01-02 22:26:07.047 24498-24498/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 54
Activity Task 棧

納尼 Activity B 呢 怎么不見(jiàn)了,這是什么鬼操作,原來(lái)是 singleTask 默認(rèn)有 clearTop 的效果,會(huì)導(dǎo)致棧內(nèi)所有在它上面的 Activity 全部出棧,這點(diǎn)一定不要忽略了
singleInstance 單實(shí)例模式
與 singleTask 相同,只是系統(tǒng)不會(huì)將任何其他 Activity 啟動(dòng)到包含實(shí)例的任務(wù)中。該 Activity 始終是其任務(wù)唯一僅有的成員;由此 Activity 啟動(dòng)的任何 Activity 均在單獨(dú)的任務(wù)中打開(kāi)。也就是有此種模式的 Activity 只能單獨(dú)地位于一個(gè)任務(wù)棧中
調(diào)用 Activity 流程 :A -> B -> B -> A,這次把 B 的啟動(dòng)模式設(shè)置為 singleInstance
打印日志:
01-02 22:41:59.069 305-305/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 57
01-02 22:41:59.071 305-305/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 57
01-02 22:42:00.280 305-305/com.will.testdemo I/debugLog: launchmode.B onCreate taskId = 58
01-02 22:42:00.283 305-305/com.will.testdemo I/debugLog: launchmode.B onStart taskId = 58
01-02 22:42:02.340 305-305/com.will.testdemo I/debugLog: launchmode.B onNewIntent taskId = 58
01-02 22:42:03.658 305-305/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 57
01-02 22:42:03.659 305-305/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 57
Activity Task 棧

結(jié)合打印日志和 Activity Task ??梢钥闯?,有此種模式的 Activity 只能單獨(dú)地位于一個(gè)任務(wù)棧中,如果已經(jīng)創(chuàng)建過(guò),則調(diào)用 onNewIntent 方法 不會(huì)調(diào)用 onCreate 和 onStart
taskAffinity 屬性
taskAffinity,可以翻譯為任務(wù)相關(guān)性。這個(gè)參數(shù)標(biāo)識(shí)了一個(gè) Activity 所需要的任務(wù)棧的名字,默認(rèn)情況下,所有 Activity 所需的任務(wù)棧的名字為應(yīng)用的包名,當(dāng) Activity 設(shè)置了 taskAffinity 屬性,那么這個(gè) Activity 在被創(chuàng)建時(shí)就會(huì)運(yùn)行在和 taskAffinity 名字相同的任務(wù)棧中,如果沒(méi)有,則新建 taskAffinity 指定的任務(wù)棧,并將 Activity 放入該棧中。另外,taskAffinity 屬性主要和 singleTask 或者 allowTaskReparenting 屬性配對(duì)使用,在其他情況下沒(méi)有意義。
與 singleTask 結(jié)合使用,調(diào)用 Activity 流程:A -> B -> B -> A -> B,設(shè)置 B 的啟動(dòng)模式為 singleTask ,并設(shè)置 taskAffinity
<activity
android:name="com.will.testdemo.launchmode.A">
</activity>
<activity
android:name="com.will.testdemo.launchmode.B"
android:launchMode="singleTask"
android:taskAffinity="com.will.testdemo.task1">
</activity>
打印日志:
01-02 23:13:29.179 16793-16793/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 59
01-02 23:13:29.180 16793-16793/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 59
01-02 23:13:31.800 16793-16793/com.will.testdemo I/debugLog: launchmode.B onCreate taskId = 60
01-02 23:13:31.801 16793-16793/com.will.testdemo I/debugLog: launchmode.B onStart taskId = 60
01-02 23:13:33.740 16793-16793/com.will.testdemo I/debugLog: launchmode.B onNewIntent taskId = 60
01-02 23:13:34.928 16793-16793/com.will.testdemo I/debugLog: launchmode.A onCreate taskId = 60
01-02 23:13:34.931 16793-16793/com.will.testdemo I/debugLog: launchmode.A onStart taskId = 60
01-02 23:13:36.203 16793-16793/com.will.testdemo I/debugLog: launchmode.B onNewIntent taskId = 60
01-02 23:13:36.204 16793-16793/com.will.testdemo I/debugLog: launchmode.B onStart taskId = 60
Activity Task 棧

B 被創(chuàng)建時(shí),因沒(méi)有 com.will.testdemo.task1 的任務(wù)棧,于是新建任務(wù)棧,并把 B 放入棧內(nèi)。繼續(xù)創(chuàng)建 A,由于 A 沒(méi)有設(shè)置啟動(dòng)模式,則放入 com.will.testdemo.task1 棧中。再一次啟動(dòng) B,因棧內(nèi)有 B 實(shí)例,所以系統(tǒng)就把 B 調(diào)到棧頂,由于 singleTask 默認(rèn)有 clearTop 的效果,導(dǎo)致棧內(nèi)所有在它上面的 Activity 全部出棧,所以最后 com.will.testdemo.task1 棧內(nèi)只有 B 一個(gè)實(shí)例
總結(jié)
上面廢話了那么多,那么這些啟動(dòng)模式到底什么時(shí)候使用呢,這里列出部分使用場(chǎng)景以供參考。
| launchMode | 使用場(chǎng)景 |
|---|---|
| singleTop | 適合啟動(dòng)同類型的 Activity,例如接收通知啟動(dòng)的內(nèi)容顯示頁(yè)面 |
| singleTask | 適合作為程序入口 |
| singleInstance | 適合需要與程序分離開(kāi)的頁(yè)面,例如鬧鈴的響鈴界面 |