前言
在使用者體驗中,比方說我們打開電子郵件的App,會跳出一個顯示所有message的列表的Activity,當我們點擊任一個message的row,就會跳出另一個畫面顯示其內容,這些無痕的使用者體驗都會被保存在同一個Task裡面,即使我們打開的是不同App的Activity也是一樣。Task就是當使用者在執行特定工作時會觸發啟動的Activity的集合。這些Activity會依照其打開的次序排列在一個堆疊(back stack)裡面。
機制說明
- 當我們點一個App的icon時,該App的Task就會到前台來。如果該App沒有使用中,那就會創造一個新的Task,並將該App的main activity作為該Task的root activity。反之,就使用之前的紀錄。
- 假使我們由main activity啟動新的Activity,main activity就會stopped,新的activity就會focus並處在該stack的最上方。
- 若又有新的activity由之前的activity啟動,新產生的activity就會push到該堆疊的最上方,依照著開啟的次序排列。反之,若按back鍵,目前focus的activity就會被pop掉(destroyed),前一個activity就會resume並且恢復之前的UI狀態。此乃last in, first out的物件結構。
- 若持續按back,activity就會一個一個被彈出,最後只剩root activity。若root activity也被彈出,那整個stack就不再存在了。
- 若啟動新的Task或是按下home button回到home screen,目前的整個Task會被移到背景去,裡面全部的activity會處在stopped狀態。即使移到背景,Task的整個back stack還是完整的,當使用者啟動背景中的Task,最上方focus的activity會被resume,stack內的其他activity也還是完整的。
儲存Activity狀態
在Back stack內處於stopped狀態的Activity可能會因為系統缺乏記憶空間而被消除(destroyed),即使被清除了,系統還是會知道在這個Back stack內有這個Activity,當需要重建它時,此時資料已經消失,所以最好要先實作好回呼
onSaveInstanceState()
,將狀態資訊儲存以備不時之需。
相關的Activity狀態儲存可以參考官方文件。
Tasks管理
以下要介紹如何對Task和back stack進行管理。管理的相關做法有兩個:
- 設定<Activity>下的屬性
- 設定Intent中的Flags
定義啟動模式(Launch Modes)
啟動模式允許你去定義新啟動的Activity和目前的Task的關聯性為何。定義啟動模式的方法有兩種:
- 使用Manifest file:在Manifest file定義Activity時,可以說明該activity啟動時與task的關係。
- 使用Intent flag:在呼叫startActivity()時,可以加上Intent的flag來指定新啟動的Activity和目前task的關係。
以Manifest file定義
在Manifest file的屬性launchMode中,有四種模式你可以去定義:- standard(預設):只要一啟動一個Activity,就會創造一個它的實例並且將intent導向該實例。所以一個任務裡面同一個Activity可以有一個以上。
- singleTop:這個重點在於會讓Back stack的Top的Activity不會重複,比方說top的Activity又再啟動自己一次,那該intent會透過onNewIntent()被遞送到已存在的Activity的實例。
- singleTask:啟動這樣的Activity會有兩種結果,第一個是開啟新的Task並讓該Activity做為root Activity。第二是如果該Activity已有存在的實例,系統會透過onNewIntent()將intent導到已存在的實例。一個Activity的實例同時間內只會存在一個。
- singleInstance:跟singleTask一樣,差異在於該實例所在的task裡面只會有一個Activity(該back stack裡面只會有一個Activity)。從這樣的Activity開啟的任何Activity都會在新的task內啟動。
以Intent flag定義
透過加入flag在startActivity()可以定義activity和task之間的關係:
- FLAG_ACTIVITY_NEW_TASK:會有兩種情況,第一種,會開啟一個新的task。第二種,如果該activity已經有一個task在運作,那該task會被呼叫到前台,並且會恢復最後的狀態,Activity會在onNewIntent()裡面接收到intent。和singleTask相同。
- FLAG_ACTIVITY_SINGLE_TOP:如果正被啟動的activity是目前的activity,亦即,在back stack的top,那已存在的實例會收到onNewIntent()呼叫,而不會創造新的實例。和singleTop相同。
- FLAG_ACTIVITY_CLEAR_TOP:如果正被啟動的activity就在目前的task內,那就不會啟動新的實例,而且會將在該activity上面的所有activity都消除(destroyed),intent會透過onNewIntent()被遞送到該activity被保留(resumed)的實例(目前在top)。沒有對應的launchMode屬性。
FLAG_ACTIVITY_CLEAR_TOP經常和FLAG_ACTIVITY_NEW_TASK同時使用。
Affinity的處理
Affinity是用來表示activity偏向屬於哪個task。
預設上,同一個app裡面全部的activity彼此都有一樣的affinity,這表示同一個app內的所有activities都會屬於同一個task。當然開發者可以透過屬性 taskAffinity 來設定該activity的affinity,要注意此屬性的值是一個string,當我們要設定自己的affinity時,不可以設成跟<manifest>裡面預設package名稱一樣,因為那就是系統用預設的affinity。
affinity會在兩種情況下使用:
- Intent啟動了包含FLAG_ACTIVITY_NEW_TASK的activity。
預設上,假設activity A屬於task A,且activity A呼叫了startActivity()來啟動activity B,則activity B就會被推到task A去。但如果startActivity()內含有此flag,系統會幫新啟動的activity尋找有相同affinity的task給它,如果沒有這樣的task,才會開一個新的task給這個activity。
如果因為這個flag讓系統是開了新的task給新啟動的activity,那當使用者按下home鍵,理應當有回到此新task的途徑 - 該activity的allowTaskReparenting屬性設定為true。
這是允許activity離開啟動它的task到另一個有一樣affinity且被移動到前台的task的屬性。
清除Back Stack
若離開Task一段時間,系統會把Task裡面的activity都清除掉,只留下root activity,等使用者再度啟用該task時,只會恢復並顯示root activity。或是修改activity屬性改變此預設行為:- alwaysRetainTaskState:若在Task的root activity設定此屬性為true,則預設行為就不會發生,系統將保留task內所有activities一段時間。
- clearTaskOnLaunch:若在Task的root activity設定此屬性為true,和alwaysRetainTaskState相反,系統會將task內除了root activity之外的所有activities都清除,不管使用者離開多久,只要離開該task然後回來,一定看到的是該task的起始狀態。
- finishOnTaskLaunch:和clearTaskOnLaunch相同,唯一的差異是它是使用在個別activity上,當離開task又啟動時,將此屬性設定為true的activity就會消失。
開始一個Task
若要讓某個activity作為Task的進入點(entry),可以透過屬性設定:
- 將action設定成"android.intent.action.MAIN"
- 將category設定成"android.intent.category.LAUNCHER"
留言
張貼留言