Activity啟動模式的選擇

2020-10-01 16:00:52

大多數應用不應打破 Activity 和任務的預設行為。如果您確定需要讓 Activity 改變預設行為,請謹慎操作,並且務必要測試該 Activity 在以下情況下的可用性:啟動期間以及您通過返回按鈕從其他 Activity 和任務返回該 Activity 時。務必要測試是否存在可能與使用者預期的行為衝突的導航行為。

四種啟動模式

1.standard

預設值。系統在啟動該 Activity 的任務中建立 Activity 的新範例,並將 intent 傳送給該範例。Activity 可以多次範例化,每個範例可以屬於不同的任務,一個任務可以擁有多個範例。

standard模式的Activity可以有多個ActivityRecord加入不同的task,同一個task也可存在多個ActivityRecord,並且ActivityRecord還可相鄰

假設Activity A啟動Activity B,B的啟動模式為standard模式

B的ActivityRecord預設會放在A的ActivityRecord所在的task裡,即使B和A的taskAffinity不同也會如此,這也意味著如果B和A屬於不同的應用,B的ActivityRecord也會放在A的ActivityRecord所在的task裡。

但是下面兩種情況不會將A和B的ActivityRecord放在同一個task裡:

如果Activity A的啟動模式為singleInstance,則會查詢整個回退棧,直到找到和B相關的task,然後把B的ActivityRecord放到該task裡,如果沒有找到相關的task,則新建task,將B的ActivityRecord放到新task裡。後面會介紹如何判斷Activity和某個task相關。

如果Activity A的啟動模式為singleTask,並且Activity A和Activity B的taskAffinity不一樣,那麼也會查詢整個回退棧,直到找到和B相關的task,然後把B的ActivityRecord放到該task裡。

2.singleTop

如果當前任務的頂部已存在 Activity 的範例,則系統會通過呼叫其 onNewIntent() 方法來將 intent 轉送給該範例,而不是建立 Activity 的新範例。Activity 可以多次範例化,每個範例可以屬於不同的任務,一個任務可以擁有多個範例(但前提是返回堆疊頂部的 Activity 不是該 Activity 的現有範例)。

3.singleTask

棧內複用模式, 只要Activity在一個棧中存在, 多次呼叫時, 都不會建立範例, 即單例模式,系統會回撥 onNewIntent(),

假設Activity A的啟動模式為singleTask,那麼和Activity A的ActivityRecord放在同一個task裡的ActivityRecord所對應的Activity,必須與Activity A的taskAffinity相同。也就是說,Activity A的ActivityRecord只會和同一應用的其它Activity的ActivityRecord放在同一個task裡,並且這些同一應用的其它Activity不能設定特殊的taskAffinity。

啟動SingleTask範例, 範例會置於棧頂, 並清除其上面範例, 具有clearTop的效果.

只要兩個Activity的taskAffinity屬性一致,即使其中有一個Activity的啟動模式為singleTask,它們對應的ActivityRecord會放在同一個task裡,不管是從某個Activity跳轉到singleTask型別的Activity,還是從singleTask型別的Activity跳轉到其他Activity都是如此,除非跳轉的其他Activity的啟動模式是singleInstance。

4.SingleInstance

單範例模式, 啟動時, 系統會為其創造一個單獨的任務棧, 以後每次使用, 都會使用這個單例, 直到其被銷燬, 屬於真正的單例模式.

該啟動模式和singleTask類似,singleInstance模式的Activity在整個回退棧只可以有一個ActivityRecord,也就是說它只能屬於某一個task,不可在多個task裡存在ActivityRecord,並且它所在的task不可再有其它Activity的ActivityRecord,即使是同一個應用內的其它Activity,也不可有它們的AcvitityRecord。

使用 Intent 標記

FLAG_ACTIVITY_NEW_TASK = .singleTask

FLAG_ACTIVITY_SINGLE_TOP = singleTop

FLAG_ACTIVITY_CLEAR_TOP如果要啟動的 Activity 已經在當前任務中執行,則不會啟動該 Activity 的新範例,而是會銷燬位於它之上的所有其他 Activity,並通過 onNewIntent() 將此 intent 傳送給它的已恢復範例(現在位於堆疊頂部),FLAG_ACTIVITY_CLEAR_TOP 最常與 FLAG_ACTIVITY_NEW_TASK 結合使用。將這兩個標記結合使用,可以查詢其他任務中的現有 Activity,並將其置於能夠響應 intent 的位置

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有這個標記的activity不會出現在歷史列表中,等同於xml中的android:execludeFromRecents="true";

處理親和性

「親和性」表示 Activity 傾向於屬於哪個任務。預設情況下,同一應用中的所有 Activity 彼此具有親和性。因此,在預設情況下,同一應用中的所有 Activity 都傾向於位於同一任務,如果一個Activity沒有顯式的指明該 Activity的taskAffinity,那麼它的這個屬性就等於Application指明的taskAffinity,如果 Application也沒有指明,那麼該taskAffinity的值就等於包名

在不同應用中定義的 Activity 可以具有相同的親和性,或者在同一應用中定義的 Activity 也可以被指定不同的任務親和性,使用 <activity> 元素的 taskAffinity 屬性修改任何給定 Activity 的親和性

(1)當啟動 Activity 的 intent 包含 FLAG_ACTIVITY_NEW_TASK 標記時。

預設情況下,新 Activity 會啟動到呼叫 startActivity() 的 Activity 的任務中。它會被推播到呼叫方 Activity 所在的返回堆疊中。但是,如果傳遞給 startActivity() 的 intent 包含 FLAG_ACTIVITY_NEW_TASK 標記,則系統會尋找其他任務來容納新 Activity。通常會是一個新任務,但也可能不是。如果已存在與新 Activity 具有相同親和性的現有任務,則會將 Activity 啟動到該任務中。如果不存在,則會啟動一個新任務。

(2)如果該Activity的allowTaskReparenting設定為true,它進入後臺,當一個和它有相同affinity的Task進入前臺時,它會重新宿主,進入到該前臺的task中。

清除返回堆疊

如果使用者離開任務較長時間,系統會清除任務中除根 Activity 以外的所有 Activity。當使用者再次返回到該任務時,只有根 Activity 會恢復。系統之所以採取這種行為方式是因為,經過一段時間後,使用者可能已經放棄了之前執行的操作,現在返回任務是為了開始某項新的操作。

您可以使用一些 Activity 屬性來修改此行為:

alwaysRetainTaskState

如果在任務的根 Activity 中將該屬性設為 "true",則不會發生上述預設行為。即使經過很長一段時間後,任務仍會在其堆疊中保留所有 Activity。

clearTaskOnLaunch

如果在任務的根 Activity 中將該屬性設為 "true",那麼只要使用者離開任務再返回,堆疊就會被清除到只剩根 Activity。也就是說,它與 alwaysRetainTaskState 正好相反。使用者始終會返回到任務的初始狀態,即便只是短暫離開任務也是如此。

finishOnTaskLaunch

該屬性與 clearTaskOnLaunch 類似,但它只會作用於單個 Activity 而非整個任務。它還可導致任何 Activity 消失,包括根 Activity。如果將該屬性設為 "true",則 Activity 僅在當前對談中歸屬於任務。如果使用者離開任務再返回,則該任務將不再存在。

Activity,回退棧,Task之間的關係

Activity啟動時ActivityManagerService會為其生成對應的ActivityRecord記錄,並將其加入到回退棧(back stack)中,另外也會將ActivityRecord記錄加入到某個Task中。請記住,ActivityRecord,backstack,Task都是ActivityManagerService的物件,由system_server程序負責維護,而不是由應用程序維護。

在回退棧裡屬於同一個task的ActivityRecord會放在一起,也會形成棧的結構,也就是說後啟動的Activity對應的ActivityRecord會放在task的棧頂。

task其實是由ActivityRecord組成的棧,多個task以棧的形式組成了回退棧,ActivityManagerService移動回退棧裡的ActivityRecord時以task為單位移動。

假設Activity的跳轉順序:A–>B–>C,A,B,C對應的ActivityRecord屬於同一個Task,此時從C跳轉至D,再跳轉至E,C和D不屬於同一個Task,D和E屬於同一個Task,那現在的back stack結構如下所示:

現在A,B,C屬於task1,C在task1的棧頂,D,E屬於task2,E在task2的棧頂。也可以看出來task2位於整個回退棧的棧頂,也就是說task2在task1的上面。如果此時不斷按回退鍵,看到的Activity的順序會是E–>D–>C–>B–>A。

另外需注意,ActivityManagerService不僅會往回退棧裡新增新的ActivityRecord,還會移動回退棧裡的ActivityRecord,移動時以task為單位進行移動,而不會移動單個AcitivityRecord。還是針對上面的例子,假設此時按了Home鍵,那麼會將Home應用程式(也叫做Launcher應用程式)的task移動至棧頂,那麼此時回退棧如下所示:

可以看到Home應用程式的Activity H對應的Activity Record移動到了回退棧的棧頂。Home應用程式的Activity H對回退按鍵的響應做了特殊處理,如果此時按回退鍵,是看不到Activity E的。

如果此時通過Launcher程式再開啟Activity A所在的應用,那麼會顯示Activity C,因為會將Activity A對應的Activity Record所在的task移動至回退棧的棧頂,此時回退棧如下所示:

此時如果按返回鍵,那麼Activity的顯示順序是:C–>B–>A–>H,不會顯示E

假設Activity A和Activity B的啟動模式都是standard,二者taskAffinity屬性值不一樣,從Activity A跳轉至Activity B,那麼它們對應的ActivityRecord會屬於同一個task。

假設Activity A的啟動模式是standard,Activity B的啟動模式singleTask,二者taskAffinity屬性值一樣,此時從Activity A跳轉至Activity B,那麼它們對應的ActivityRecord會屬於同一個task。因為只要兩個Activity的taskAffinity屬性一致,即使其中有一個Activity的啟動模式為singleTask,它們對應的ActivityRecord會放在同一個task裡,不管是從某個Activity跳轉到singleTask型別的Activity,還是從singleTask型別的Activity跳轉到其他Activity都是如此,除非跳轉的其他Activity的啟動模式是singleInstance。

假設Activity A的啟動模式是standard,Activity B的啟動模式singleTask,二者taskAffinity屬性值不 一樣,此時從Activity A跳轉至Activity B,那麼它們對應的ActivityRecord會屬於不同的Task。

注意

  • 1) 從Launcher程式啟動應用時,會先查詢所有task,看是否有相關task,如果已有相關task,則會將相關task移動到回退棧的棧頂,然後顯示棧頂Activity。查詢相關task時,需看task是否和應用的入口Activity相關,入口Activity是指在AndroidManifest.xml裡宣告IntentFilter時,註明category為android.intent.category.LAUNCHER的Activity。如果入口Activity的啟動模式為singleTask,不僅會將相關task移動到回退棧的棧頂,還會將該task裡位於入口Activity之上的其它ActivityRecord全部清除掉
  • 2) 通過最近應用程式,切換應用時,會直接將應用圖示對應的task移動到回退棧的棧頂,這樣即使task裡有singleTask型別的ActivityRecord,在它之上的ActivityRecord也不會被清除
  • 3) 可以通過adb shell dumpsys activity activties檢視系統task情況