Activity的正確打開方式

adb shell dumpsys activity

輸入這個命令可以得到一個清晰的 Task 視圖,比如你有多少個 Task ,哪些 activity 在其對應的 Task 等相關信息。

下圖是一張運行這個命令的輸出截圖。

20160214_01.png

從圖中可以看出,有兩個 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 中寫入啓動模式,如下:

  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.six.adv">
  2.     <Activity android:name=".A"/>
  3.     <Activity android:name=".B" android:launchMode="singleTask"/>
  4. </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 中這樣寫:

  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.six.adv">
  2.     <activity android:name=".A"/>
  3.     <activity android:name=".B" android:launchMode="singleTask" android:taskAffinity="task2"/>
  4. </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 屬性值是一樣滴。

用邏輯來表達,就像是這樣:

  1. --> B
  2.  
  3.   if( taskAffinity 屬性相同) { 
  4.     A  B 在同一個 Task 
  5.   }
  6.   else { 
  7.     B 在新的 Task 中,並且此 Task  affinity 屬性值就是 B 
  8.   }

那麼這個例子中, 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 如下:

  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.six.adv">
  2.     <activity android:name=".A"/>
  3.     <activity android:name=".B" android:launchMode="singleTask" android:taskAffinity="task2"/>
  4.     <activity android:name=".C" android:launchMode="singleTask" android:taskAffinity="task2"/>
  5. </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 小結

  1. if( 發現一個 Task  affinity == Activity  affinity ){
  2.     if(此 Activity 的實例已經在這個 Task 中){
  3.         這個 Activity 啓動並且清除頂部的 Acitivity ,通過標識 CLEAR_TOP 
  4.     } else {
  5.         在這個 Task 中新建這個 Activity 實例
  6.     }
  7. } else { // Task 的 affinity 屬性值與 Activity 不一樣
  8.     新建一個 affinity 屬性值與之相等的 Task
  9.     新建一個 Activity 的實例並且將其放入這個 Task 之中
  10. }

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)時,你的程序必然會崩。報錯如下:

  1. AndroidRuntimeException :   
  2.             "Calling startActivity() from outside of an Activity context 
  3.             requires the FLAG_ACTIVITY_NEW_TASK flag. 
  4.             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 之中:

  1. // "this" is a service
  2. Intent it = new Intent(this, ActivityB.class); 
  3. it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  4. this.startActivity(it);

瞅見沒?這纔是 Activity 的啓動模式的正確打開方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章