-
原文作者:songzhw
-
譯文出自:掘金翻譯計劃
-
譯者: Liz
-
校對者: mypchas6fans,hackerkevin
adb shell dumpsys activity
輸入這個命令可以得到一個清晰的 Task 視圖,比如你有多少個 Task ,哪些 activity 在其對應的 Task 等相關信息。
下圖是一張運行這個命令的輸出截圖。
從圖中可以看出,有兩個 Task (#103, #102) 。
Task #103 : affinity = “cn.six.task2”, size = 3 (它裏面有三個activity)
— Activity One
— Activity Three
— ActivityTwo
Task #102 : affinity = “cn.six.adv”, size = 1
— Activity One
擁有了這個神奇的命令—— “adb shell dumpsys activity” ,我們就可以更好地探索 Activity 的啓動模式啦…
Default
到達此 activity 的 Intent ,系統會默認地在目標 Task 中創建一個新的實例並將默認的啓動模式屬性設置爲 "default" 。
“Default” 是 activity 的默認啓動模式,也就是說當你未給 activity 指定啓動模式的時候,系統默認會給一個 “Default” 作爲它的啓動模式。
SingleTop
如果一個啓動模式爲 SingleTop 的 activity 實例在目標棧頂,intent 啓動該 activity 時系統將通過 onNewIntent 的方法將 intent 傳遞給已有的那個實例而不會新創建一個的實例。
注意:並不是清除棧頂的 activity !!!(也就是說只要棧頂不是本 activity ,都會創建新的實例,是本 activity 則重用不新建)。
SingleTask
這個是最難理解的,下文中我會搭配幾個例子來細細講解這個複雜的啓動模式。
1. A(Default) -> B(singleTask)
我們有兩個 Activity ,A 和 B ,其中 B 是 SingleTask 模式,現在從 A 跳轉到 B 。
首先在 Manifest 中寫入啓動模式,如下:
- <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.six.adv">
- <Activity android:name=".A"/>
- <Activity android:name=".B" android:launchMode="singleTask"/>
- </manifest>
Android 官方文檔中提到 “ intent 啓動一個(SingleTask) 的 Activity ,系統會將這個 Activity 創建在一個新的 Task 根部”。 SO ,聽起來會是這個樣子?
Task 1 | Task 2 |
---|---|
A | B |
但實際上,當我們運行命令 “adb shell dumpsys activity” 時,發現 B 這貨詭異地和 A 出現在一個 Task 中。
Task 1 | Task 2 |
---|---|
B |
|
A | (null) |
這個問題有一點小難表達,因爲這裏面 B 使用了 android:taskAffinity 屬性。 後文中會有詳解。
2. A(Default) -> B(singleTask) : B has a taskAffinity attribute
在 manifest 中這樣寫:
- <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.six.adv">
- <activity android:name=".A"/>
- <activity android:name=".B" android:launchMode="singleTask" android:taskAffinity="task2"/>
- </manifest>
在這裏, A 啓動 B 的效果就不一樣啦。如下:
Task 1 | Task 2 |
---|---|
A | B |
這個和上一個例子的唯一不同就是屬性 “android:taskAffinity” 。 當你不聲明 affinity 屬性, 那麼 activity 就會以包名作爲其默認值。在這個例子中, 默認的 affinity 值就是 “cn.six.adv” 。
當 A 啓動 B ,即使 B 的啓動模式是 singleTask ,但也只有當 android:taskAffinity 屬性和 A 不同時纔會創建新的 task 。
看到這裏,第一個例子是不是就頓時豁然開朗? 爲什麼 A 和 B 在同一個 Task 中呢?因爲它們的 taskAffinity 屬性值是一樣滴。
用邏輯來表達,就像是這樣:
- A --> B
- if( taskAffinity 屬性相同) {
- A 和 B 在同一個 Task 中
- }
- else {
- B 在新的 Task 中,並且此 Task 的 affinity 屬性值就是 B 的
- }
那麼這個例子中, A 跳轉 B, B 的啓動模式是 “singleTask” , 並且 B 的 taskAffinity 不是 “cn.six.adv” 。 所以 B 會在一個新建的 Task 中。
Task 1 (affinity=”cn.six.adv”) | Task 2 (affinity=”task2″) |
---|---|
A | B |
3. A(default) -> B(singleTask) -> C(singleTask) -> B(singleTask)
manifest 如下:
- <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.six.adv">
- <activity android:name=".A"/>
- <activity android:name=".B" android:launchMode="singleTask" android:taskAffinity="task2"/>
- <activity android:name=".C" android:launchMode="singleTask" android:taskAffinity="task2"/>
- </manifest>
(1). A -> B
Task 1 (affinity=”cn.six.adv”) | Task 2 (affinity=”task2″) |
---|---|
A | B |
(2) A -> B -> C
因爲 C 的 affinity 是 “task2” ,而 Task 中已經有一個和它一樣屬性值的 B ,所以 C 會被放在 Task 2 中。
Task 1 (affinity=”cn.six.adv”) | Task 2 (affinity=”task2″) |
---|---|
A | C B |
(3) A -> B -> C -> B
首先看一下實際結果
Task 1 (affinity=”cn.six.adv”) | Task 2 (affinity=”task2″) |
---|---|
A | B |
好奇怪啊! C 去哪裏啦?
事情呢,是這個樣子滴。 C->B , B 的啓動模式是 singleTask 而且它的 affinity 屬性值是 “task2”, 當系統發現有一個 affinity 屬性值爲 task2 的 Task 2 所以就把 B 放進去了。但是, 其中已經有一個 B 的實例在 Task 2 之中。 所以系統會將已有的 B 的實例賦予一個 CLEAR_TOP(清除頂部)標誌。所以 C 是這麼沒的。
4. SingleTask 小結
- if( 發現一個 Task 的 affinity == Activity 的 affinity ){
- if(此 Activity 的實例已經在這個 Task 中){
- 這個 Activity 啓動並且清除頂部的 Acitivity ,通過標識 CLEAR_TOP
- } else {
- 在這個 Task 中新建這個 Activity 實例
- }
- } else { // Task 的 affinity 屬性值與 Activity 不一樣
- 新建一個 affinity 屬性值與之相等的 Task
- 新建一個 Activity 的實例並且將其放入這個 Task 之中
- }
SingleInstance
SingleInstance 要比 SingleTask 好理解很多。
如果一個 Activity 的啓動模式爲 SingleInstance, 那麼這個 Activity 必定會在一個新的 Task 之中, 並且這個 Task 之中有且只能有一個 Activity 。
再來一波栗子。
1. A(default) –> B(singleInstance) –> C(default)
(1). A -> B
Task 1 | Task 2 |
---|---|
A | B |
(2). A -> B -> C
擁有 “singleInstance” 啓動模式的 activity 不予許其他任何 Activity 在它的 Task 之中。所以它是這個 Task 之中的獨苗啊。當它跳轉另外一個 activity 時, 那個 Activity 將會被分配到另外一個 Task 之中——就像是 intent 被賦予了 FLAG_ACTIVITY_NEW_TASK 標誌一樣。
由於 B 需要一個只能容納它的 Task , 所以 C 會被加上一個 FLAG_ACTIVITY_NEW_TASK 標識。所以 C(default) 變成了 C(singleTask) 。
然後結果變成了這樣:
Task 1 | Task 2 |
---|---|
c A | B |
注:如果跳轉的流程是 “A(default) –> B(singleTask) –> C(default)”, 那麼結果會是這樣:
Task 1 | Task 2 |
---|---|
A | C B |
如何去運用啓動模式呢?
假如, 你需要在 service 在後臺中做一些耗時操作,當它完成時, 你需要從此 service 中跳轉進入一個 Activity 中,你會怎樣做?
Service 是 Context 一種擴展, 它含有 startActivity(intent) 方法。但是當你調用service.startActivity(intent)時,你的程序必然會崩。報錯如下:
- AndroidRuntimeException :
- "Calling startActivity() from outside of an Activity context
- requires the FLAG_ACTIVITY_NEW_TASK flag.
- Is this really what you want?"
這就是上文中提到的。當一個 Activity A 跳轉進入另一個 Activity B (它們的啓動模式都爲默認的 default ), 所以這個 B 會和 A 在一個 Task 之中。但是當你想讓 service 跳轉到 Activity B, 由於 service 並不是一個 Activity , 所以它沒有相關的 task 信息。所以 Service 不會出現在 Activity 的任務棧之中。這種情況下,Activity B 就不知道自己的 Task 在哪裏了。
爲了解決上述問題,我們可以告訴 Activity B 它應該在一個新的 Task 之中:
- // "this" is a service
- Intent it = new Intent(this, ActivityB.class);
- it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- this.startActivity(it);
瞅見沒?這纔是 Activity 的啓動模式的正確打開方式。