標籤:
一個應用中包含了多個Activity執行個體,每個Activity都有各自的action,每個Activity也可以啟動其他Activity,如一個Email應用程式應包含一個顯示Email資訊列表的Activity。當使用者點擊列表中的某一項時,顯示詳細內容的Activity將被啟動。
本文將介紹Activity的棧和後退棧(Tasks and Back Stack)的相關知識,您需訪問官方原文,您可以點擊這個連結:《Tasks and Back Stack》。
一個Activity甚至可以啟動其他應用程式中的Activity。例如您打算啟動一個發送郵件的Activity,那麼使用Intent隱式來啟動,該Intent中應包含值為“send”的action、並包含一些data資料,比如郵件地址和資訊內容。這樣,啟動的Email的Activity就好像是本應用的一部分。Android使用同一任務棧來使得Activity之間實現無縫切換的(Android maintains this seamless user experience by keeping both activities in the same task)。
任務棧和返回棧(Tasks and Back Stack)
任務棧是一個存放Activity集合的地方。通過一個任務棧,使用者可以完成一項操作(如傳送簡訊的操作、照相的操作 等)(A task is a collection of activities that users interact with when performing a certain job)。
Home鍵是多數任務棧啟動的地方(The device Home screen is the starting place for most tasks)。當使用者點擊某個應用的表徵圖時,這個應用程式的任務棧就被切換至前台;如果任務棧不存在(即使用者還從未啟動過該應用),那麼系統會建立一個任務棧,這個應用的主Activity將被壓至任務棧的棧底(a new task is created and the “main” activity for that application opens as the root activity in the stack)。
當Activity A 啟動Activity B時,Activity B會被壓入任務棧的棧頂,並獲得焦點。而Activity A將處於Activity的下面,且處於stop狀態,處於stop狀態的Activity仍持有與使用者互動的資訊。當使用者點擊了back鍵時,Activity B將從棧中彈出,並被destroy,Activity A恢複resume狀態。Activity在棧中的順序永遠不會重新排列(Activities in the stack are never rearranged),Activity只能從棧中彈出或是壓入(only pushed and popped from the stack)。任務棧遵循後進先出的原則(last in, first out)。如所示:
使用者點擊back鍵時,棧中的Activity將依次被彈出,直到退回到案頭。當所有Activity都被彈出後,任務棧將不再存在(When all activities are removed from the stack, the task no longer exists)。
任務棧是一組緊密結合的單元:當使用者啟動了一個Activity或點擊Home鍵退回案頭時,Activity所屬的任務棧將整體地移至前台或移至後台。移至背景任務棧中的所有Activity將處於stop狀態。但棧中的內容將保持完整,它們只是失去了焦點而已,如所示,處於背景task A中的Activity僅處於stop狀態,它們仍保留了Activity的所有資訊:
Android可以在後台一次性處理多個任務棧。然而,如果在後台管理太多的任務棧,系統可能會destroy一些Activity以騰出記憶體,導致Activity的狀態丟失。
由於在任務棧中,Activity的順序不能改變,預設情況下,若在一個應用程式中需要啟動另一個應用程式的某個Activity多次,那麼該Activity每次在被啟動時都會被重新執行個體化,如所示:
當然,您可也可以改變這種預設規則,讓一個任務棧中的Activity執行個體不能重複。這將在下面介紹。
下面對Activity和任務棧的預設規則做一總結:
當Activity A啟動Activity B 時,Activity A處於stop狀態,但Activity A中的資訊被保留,如使用者鍵入的資訊或滑動的位置。若使用者在Activity B中點擊了back鍵,Activity A將恢複狀態。
當使用者點擊了Home鍵,Activity與其所屬的任務棧將整體轉入後台,並處於stop狀態。棧中所有Activity的狀態均被保留。若使用者通過點擊應用表徵圖恢複了該應用程式,則該任務棧又被切換至前台,Activity將又處於棧頂。
Activity可以被執行個體化多次。
儲存Activity狀態(Saving Activity State)
如上所述,不在棧頂的Activity將由系統儲存其狀態,當Activity重新處於棧頂時,Activity中的內容將被恢複。事實上,開發人員應通過Activity中的回調方法主動地儲存Activity中的狀態資訊。
當Activity處於stop狀態時,系統會優先回收其執行個體以騰出記憶體。這樣的話,Activity的狀態資訊就會丟失。但是任務棧仍然記錄了該Activity 在棧中的位置,當它恢複至前台時,Activity的執行個體將重新建立(而不是resume),其內容將丟失。為了避免內容丟失,您應當重寫Activity的回調方法onSaveInstanceState()。
管理工作棧(Managing Tasks)
上面介紹的都是任務棧的預設行為,這種預設行為已經可以處理大多數情況,但有些時候,您希望打破這種預設行為,如啟動應用中的某個Activity時建立一個任務棧;或者啟動一個已存在的Activity時,希望直接將其調至前台,而不是再新建立一個Activity執行個體;或者退出任務棧時,您希望除了棧底的主Activity之外,其餘Activity均出棧 等。
為了實現上述操作,您需要在manifest檔案的<activity>標籤中配置,或在starActivity()方法中傳入的Intent參數中添加flag。
其中<activity>標籤中關於任務棧的主要屬性如下:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
常用的Intent的flag如下:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
!請注意:許多應用程式禁止改變其Activity和任務棧的預設行為。若確實需要改變其其行為,需要測試back鍵、home鍵等操作,以防與預設行為產生衝突。
定義啟動模式(Defining launch modes)
啟動模式指定了新啟動的Activity與當前任務棧中關聯模式。定義啟動模式的方式有兩種:
舉例來說,Activity A 啟動Activity B,可以在Activity B 的manifest中指定Activity B如何與當前任務棧管理,也可以通過Activity A的intent指定Activity B如何與當前任務棧管理。若程式中兩種方式都配置了,那麼以intent的指定方式為準(Activity A’s request (as defined in the intent) is honored over Activity B’s request (as defined in its manifest))。
!請注意:某些啟動模式只能在manifest中配置,而不能在intent的flag中指定;還有一些啟動模式只能在intent的flag中指定而不能在manifest中配置。
使用manifest檔案配置啟動模式(Using the manifest file)
在<activity>標籤的launchMode屬性中配置;該屬性指定了Activity在任務棧中以何種方式被啟動,有四種方式:
standard(預設):Activity可以被多次執行個體化,每個執行個體可以存在於不同任務棧中,每個任務棧可以包含多個相同的Activity執行個體。
singleTop:若Activity執行個體已處於棧頂,該Activity只是回調其onNewIntent()方法而不再建立新的執行個體。該Activity可以被執行個體化多次,每個執行個體可以存在於不同任務棧中,每個任務棧可以包含多個相同的Activity執行個體,但處於棧頂的執行個體除外。
singleTask:系統將建立一個新的任務棧,並將該Activity放入該任務棧的棧底。若該Activity執行個體在其他的任務棧中已存在,系統將不會再建立該Activity執行個體,而是回調onNewIntent()方法。一個Activity執行個體在同一時刻只能有一個。
singleInstance:與singleTask模式類似,唯一不同的是:若是將啟動的Activity壓入新建立的棧中(即啟動的Activity執行個體不存在),那麼該棧中有且僅有一個Activity執行個體,不能再有其他執行個體。
舉個例子,Android瀏覽器應用程式中的每個Activity都被指定為singleTask啟動模式。這就意味著如果您的Activity打算啟動Android瀏覽器應用程式中的Activity,那麼啟動的Activity將不會出現在您的應用程式中,而是要麼壓入新建立的任務棧中,要麼將要啟動的Activity所在的任務棧切至前台(後一種情況表明要啟動的Activity已經被啟動過,只是在後台而已)。
無論上述那種情況,當您按下back鍵的時候,系統總是切至前台任務棧中Activity。如果將某個Activity的啟動模式指定為singleTask,且該執行個體已存在,那麼系統會將該Activity所屬的任務棧整體切換至前台。如所示:
使用Intent中包含的flags參數配置啟動模式(Using Intent flags)
除了使用在manifest中配置Activity的啟動模式,還可以在代碼中使用Intent包含的flags參數,並最終將該Intent傳入startActivity()方法來配置啟動模式。常用的flag參數如下:
FLAG_ACTIVITY_NEW_TASK:建立一個新的任務棧,並將該Activity壓入棧底。若啟動的Activity已經存在於記憶體中,則系統將該Activity所在的任務棧整體切至前台,並回調該Activity執行個體的onNewIntent()方法。這相當於在manifest中配置了singleTask啟動模式。
FLAG_ACTIVITY_SINGLE_TOP:若啟動的Activity位於棧頂,則不再建立一個Activity執行個體,而只是回調其onNewIntent()方法。這相當於在manifest中配置了singleTop啟動模式。
FLAG_ACTIVITY_CLEAR_TOP:如果要啟動的Activity已經在當前任務棧中存在執行個體,那麼所有該Activity上面的Activity執行個體均destroy,這時該Activity執行個體將位於棧頂並將intent回傳至該Activity的onNewIntent()方法中。這在manifest中沒有與之匹配的啟動模式。FLAG_ACTIVITY_CLEAR_TOP經常與FLAG_ACTIVITY_NEW_TASK聯合使用。
處理affinities(Handling affinities)
affinities表示一個Activity“期望”屬於哪一個任務棧。預設情況下,同一應用的所有Activity的affinity相同。也就是,預設情況下,同一應用的所有Activity都期望屬於同一個任務棧。不過,您可以修改預設設定。比如,來自不同應用的Activity可以擁有同一個affinity,或者同一應用的不同Activity具有不同的affinity。
您可以在activity標籤中配置taskAffinity屬性來改變Activity的affinity。taskAffinity屬性是一個字串,您必須保證在<manifest>標籤中指定的包名唯一,因為Android將包名作為任務棧affinity預設屬性。
affinity會在下面兩中情形下產生作用:
- 當啟動的intent包含
FLAG_ACTIVITY_NEW_TASK的flag時。這表明啟動的Activity總是“希望”將自己壓入一個新棧中。然而,如果系統中存在一個任務棧的affinity屬性與該Activity相同,則該Activity將被壓入到這個任務棧中。如果沒有這樣的任務棧,則系統會建立一個任務棧並將該Activity壓入棧底。
若該flag使得啟動的Activity壓入了新建立的任務棧,並且使用者點擊了Home鍵,這就需要用某種方法再將該Activity切換至前台。某些實體(如notification manager)通常將Activity啟動至一個新棧的棧底中。由於這些啟動的notification不屬於本應用,所以在intent中通常加入FLAG_ACTIVITY_NEW_TASK的flag。如果您的Activity可以被這種外部實體所啟動,那麼需當心,使用者還可以使用一種獨特的方式啟動它(如在intent filter中加入CATEGORY_LAUNCHER屬性,那麼點擊啟動表徵圖,所有位於棧底的Activity所在的任務棧都符合隱式啟動條件)。
- 當Activity的
allowTaskReparenting屬性為true時。這時,啟動Activity時,該Activity屬於預設的任務棧,當Activity所屬的應用切至前台時,該Activity又回到了這個應用任務棧中。
比如說,將天氣預報應用中某個Activity的allowTaskReparenting屬性設為true時,您的應用啟動這個Activity,那麼該Activity最初屬於您的應用的任務棧,但當天氣預報應用的任務棧切至前台時,這個Activity將被壓至天氣預報應用的任務棧中。
清除後退棧(Clearing the back stack)
若使用者長時間未將任務棧切至前台,那麼該任務棧中的Activity將被destroy(不包含棧底的啟動Activity)。當使用者將該任務棧切至前台時,只有棧底的Activity被恢複。下面列出一些屬性來修改這一預設行為:
alwaysRetainTaskState:若將棧底的Activity的alwaysRetainTaskState屬性設為true,那麼即便將該Activity所屬的任務棧放在後台很長一段時間,任務棧中所有的Activity的狀態資訊都會得到保留。
clearTaskOnLaunch:若將棧底的Activity的clearTaskOnLaunch屬性設為true,那麼即便使用者將該任務棧切至背景很短時間,任務棧中所有的Activity的狀態資訊都會被清除,再次將該任務棧切至前台時,任務棧將是初始狀態。也就是說,這個屬性與alwaysRetainTaskState屬性完全相反。
finishOnTaskLaunch:該屬性與clearTaskOnLaunch類似,只是它針對的是單個Activity。
啟動任務棧(Starting a task)
您可以按照如下intent-filter來配置任務棧的啟動Activity:
<activity ... > <intent-filter ... > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> ...</activity>
使用者必須能夠隨時退出任務棧並隨時通過啟動表徵圖將任務棧切至前台
( Users must be able to leave a task and then come back to it later using this activity launcher)。所以,使用"singleTask"、 "singleInstance"這兩種啟動模式的的Activity必須具有ACTION_MAIN 和 CATEGORY_LAUNCHER的intent-filter。比如說,使用者啟動了一個具有singleTask模式的Activity,該Activity被壓入了一個新的任務棧的棧底,當使用者在該Activity中做了一些編輯工作後,點擊home鍵,將該任務棧切至後台,那麼當使用者希望再次啟動該Activity時,若該Activity為配置ACTION_MAIN 和 CATEGORY_LAUNCHER,那麼使用者將無法將啟動該Activity!若您確實希望該Activity切至後台以後,使用者不能再將它切至前台,可以在該activity標籤中將finishOnTaskLaunch屬性設為true。
Android官方文檔之App Components(Tasks and Back Stack)