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

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

啟動模式
在了解了任務(wù)和返回棧后,我們來說說啟動模式,上圖的堆棧是比較常規(guī)的,如果我們一直啟動同一個 Activity 系統(tǒng)會重復(fù)創(chuàng)建多個實例,但這不是我們想要的結(jié)果。這時候為了滿足我們的需求就需要使用 Android 提供的啟動模式來修改系統(tǒng)的默認行為。目前有四種啟動模式:standard、singleTop、singleTask 和 singleInstance。在 AndroidManifest.xml 中配置即可,如下:
<activity
android:name="com.will.testdemo.launchmode.A"
android:launchMode="standard">
</activity>
standard 默認模式
系統(tǒng)在啟動 Activity 的任務(wù)中創(chuàng)建 Activity 的新實例并向其傳送 Intent。Activity 可以多次實例化,不管這個實例是否已經(jīng)存在,而每個實例均可屬于不同的任務(wù),并且一個任務(wù)可以擁有多個實例。這種模式的 Activity 被創(chuàng)建時它的 onCreate、onStart 都會被調(diào)用。這是一種典型的多實例實現(xiàn),一個任務(wù)棧中可以有多個實例,每個實例也可以屬于不同的任務(wù)棧。在這種模式下,誰啟動了這個 Activity,那么這個 Activity 就運行在啟動它的那個 Activity 所在的棧中。
這里通過簡單的代碼來驗證,先實現(xiàn)方法來打印 Activity 的生命周期調(diào)用過程和 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 目標 Activity
*/
inline fun <reified T : Activity> Context.toActivity() {
startActivity(Intent(this, T::class.java))
}
界面很簡單就一個按鈕,就不截圖了,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>()
}
}
啟動 A 然后點擊兩下按鈕,日志如下:
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 棧,可以看出每啟動一次 A 都會創(chuàng)建一次實例,不管這個實例是否已經(jīng)存在

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

這里我們新建一個 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 棧

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

關(guān)于上文中所說的想要的任務(wù)棧,指的是 taskAffinity 屬性,手動設(shè)置所需的任務(wù)棧,這個后面會具體介紹
調(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 呢 怎么不見了,這是什么鬼操作,原來是 singleTask 默認有 clearTop 的效果,會導(dǎo)致棧內(nèi)所有在它上面的 Activity 全部出棧,這點一定不要忽略了
singleInstance 單實例模式
與 singleTask 相同,只是系統(tǒng)不會將任何其他 Activity 啟動到包含實例的任務(wù)中。該 Activity 始終是其任務(wù)唯一僅有的成員;由此 Activity 啟動的任何 Activity 均在單獨的任務(wù)中打開。也就是有此種模式的 Activity 只能單獨地位于一個任務(wù)棧中
調(diào)用 Activity 流程 :A -> B -> B -> A,這次把 B 的啟動模式設(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 只能單獨地位于一個任務(wù)棧中,如果已經(jīng)創(chuàng)建過,則調(diào)用 onNewIntent 方法 不會調(diào)用 onCreate 和 onStart
taskAffinity 屬性
taskAffinity,可以翻譯為任務(wù)相關(guān)性。這個參數(shù)標識了一個 Activity 所需要的任務(wù)棧的名字,默認情況下,所有 Activity 所需的任務(wù)棧的名字為應(yīng)用的包名,當 Activity 設(shè)置了 taskAffinity 屬性,那么這個 Activity 在被創(chuàng)建時就會運行在和 taskAffinity 名字相同的任務(wù)棧中,如果沒有,則新建 taskAffinity 指定的任務(wù)棧,并將 Activity 放入該棧中。另外,taskAffinity 屬性主要和 singleTask 或者 allowTaskReparenting 屬性配對使用,在其他情況下沒有意義。
與 singleTask 結(jié)合使用,調(diào)用 Activity 流程:A -> B -> B -> A -> B,設(shè)置 B 的啟動模式為 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)建時,因沒有 com.will.testdemo.task1 的任務(wù)棧,于是新建任務(wù)棧,并把 B 放入棧內(nèi)。繼續(xù)創(chuàng)建 A,由于 A 沒有設(shè)置啟動模式,則放入 com.will.testdemo.task1 棧中。再一次啟動 B,因棧內(nèi)有 B 實例,所以系統(tǒng)就把 B 調(diào)到棧頂,由于 singleTask 默認有 clearTop 的效果,導(dǎo)致棧內(nèi)所有在它上面的 Activity 全部出棧,所以最后 com.will.testdemo.task1 棧內(nèi)只有 B 一個實例
總結(jié)
上面廢話了那么多,那么這些啟動模式到底什么時候使用呢,這里列出部分使用場景以供參考。
| launchMode | 使用場景 |
|---|---|
| singleTop | 適合啟動同類型的 Activity,例如接收通知啟動的內(nèi)容顯示頁面 |
| singleTask | 適合作為程序入口 |
| singleInstance | 適合需要與程序分離開的頁面,例如鬧鈴的響鈴界面 |