1. 程式人生 > >【轉】Tasks and Back Stack

【轉】Tasks and Back Stack

Tasks and Back Stack

一個應用通常包括多個 activity。每個 activity應用設計為圍繞針對執行使用者特定的行為和可以啟動其它 activity。

一個 Activity也可以啟動別個應用的 Activity。當別的應用完成,你的應用會重新啟用 ,來自別個應用的 activity看起來像是自己的應用中的一樣。儘管這些 Activity處於不同的應用, Android將這些 activity維護到同一個 task中給使用者這種無縫的應用體驗。

一個 task是使用者執行一個特定的工作與使用者互動的一組特定的 Activity的集合。 Activity被安排到同一個棧 (back stack)中,其中的 activity按順序的開啟的。

桌面是絕大多數任務被啟動的地方。當用戶在應用啟動器中觸擊一個應用的圖示,這個應用就會回到前臺。如果沒有這個應用的任務存在,那麼建立一個新的 task,這個應用的 “main” Activity開啟,並且作為這個 task棧的根 activity。

噹噹前 Activity啟動另一個 Activity,新的 Activity被推到棧頂並且佔據焦點。前一個 Activity保持在棧中,但是 處於 stop狀態。當一個 activity停止掉後,系統儲存他當前的使用者介面的狀態。當用戶按 BACK鍵,當前的 Activity從棧中彈出並銷燬,前一個 Activity被啟用。棧中的 Actvity不會被重置,只會推入或者彈出。所以, back stack遵循一個後進先出的機制。

如果使用者繼續按 BACK鍵,棧中的每個 Activity都從棧中彈出顯示前一個 Activity,直到返回到桌面 (或者到這個棧開始時正在執行的 Activity)。當所有的 Activity從棧中移出了,這個棧就不存在了。

一個 Task是一個聚合單元,當用戶開啟一個新的任務,或者通過 HOME鍵回到桌面,這個 task就移動到後臺。當 task處於後臺,所有的 activity都處於 stop狀態,但是這個任務的 back stack仍是完好無損的。當新的棧佔據了焦點之後,這個棧會很簡單的失去焦點。

因為 back stack中的 activity不會被重置,如果你的應用允許你啟動一個特定的 activity多次,建立一個新的 activity的例項,並且推入棧頂。所以在這種情況下,如果使用者使用 BACK鍵導航,可能會多次看到同一個 activity。

總結 Activity 與 task 的預設的行為 :

· 當 Activity A 啟動 Activity B, Activity A停止了,但是系統會儲存他的狀態 (例如滾動條的位置以及輸入的文字資訊 )。當用戶在 B中按 BACK鍵, Activity 將繼續他之間的狀態。

· 當用戶通過按 HOME鍵的方式離開一個 task,當前的 Activity停止,並且這個 task轉到後臺。系統將保持這個 task的所有的 activity的狀態。如果稍後再繼續這個 task,這個 task將回到前臺,並且繼續之間最棧頂的 Activity。

· 如果使用者按 BACK鍵,當前 Activity彈出棧並被銷燬。棧中之前的 Activity得以繼續。當 Activity被銷燬了,系統將不再儲存其狀態。

· Activity可以被例項化多次,甚至從其它的應用中例項。

儲存Activity狀態(Saving Activity State)

正如前面討論的那樣,當 activity停止的時候,系統預設的行為會儲存他的狀態。這樣的話,當導航到上一個 Activity,他的介面會像他離開時的一樣展現給使用者。然而,你可以且應該在回撥方法中主動儲存你的狀態,以避免你的 Activity被銷燬掉之且必須重新建立。



當系統停止掉你的 Activity,系統可能為了重新獲得記憶體而完全銷燬掉他。當這種情況發生了, activity的狀態資訊會丟失掉。這種情況發生了,系統仍然知道這些 activity在 back stack有一個位置的,但是當 activity到棧頂的時候,系統會重新建立他,而不是 resume他了。為了避免丟失使用者的工作,你需要實現 onSaveInstanceState()主動儲存你的 activity狀態。

管理Task(Managing Tasks)

像前面描述的一樣, Android管理 task與 back stack的方法是,將所有的開啟的 activity連續的放進同一個 task中,並且放到一個“後入,先出”的堆疊中,面對大多數的應用,你不必關心你的 activity是如何與 task關聯的,不必關心你的 activity是如何存在於 back stack中的。然而你可能想打破這種常態的行為。也許你想要讓你應用的 activity被啟動的時候去開啟一個新的 task(而不是將其放入當前的棧中 );或者,當你啟動一個 activity的時候,你想將其轉到一個已存在的他的實體中去 (而不是在 back stack 的棧頂建立一個新的實體 );或者,當你離開這個 task時,你想讓你的 back stack棧中除根 activity的所有的 activity都被清理掉。

你可以做到這些甚至更多,用 <activity>的 manifest元素的屬性以及你傳遞給 startActivity()的 intent的 flag。

關於這些,你可以用的 <activity>的主要屬性如下:

taskAffinity

lauchMode

allowTaskReparenting

clearTaskOnLaunch

alwaysRetainTaskState

finishOnTaskLaunch

主要的 intent的 flag如下:

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_SINGLE_TOP

定義啟動模式(Defining launch modes)

Launch mode 允許你定義一個 activity的新的實體與當前 task是如何關聯的。你可以用兩種方式定義不同的啟動模式:

· 使用 manifest 檔案

當在 manifest中宣告你的 activity的時候,你可以指定當他啟動時他如何與 task關聯。

· 使用 intent的 flag

當你呼叫 startActivity()的時候,你可以為 intent 設定一個 flag以宣告這個新的 activity應如何與當前的 task關聯。

照這樣,如果 Activity A啟動 Activity B,可以在 manifest中定義 B如何與當前的 task如何關聯,也可以在 Activity A中要求 Activity B與當前 task 如何關係。如果兩個 Activity都定義了 Activity B應該如何與當前 Task關聯,那麼 Activity A的要求會更為榮幸的得到應用。

使用manifest檔案(Using the manifest file)
當在 manifest檔案中宣告一個 activity的時候,你可以通過 <activity>的 lauchMode屬性來指定 activity應該怎樣與 task關聯。

launchMode可以指定一個指定其 activity應該如何啟動到一個 task中。這裡有四種不同的 launchMode你可以使用。

“standard”(預設模式 )

預設模式。系統會從啟動他的 task中建立一個該 Activity的新例項 ,並且導向他。一個 Activity可以被例項化多次,每個例項可以屬於不同的 task,一個任務可以擁有多個例項。

“singleTop”

如果一個 Activity的例項已經存在當前的 task的棧頂了,系統會通過 onNewIntent()方法將 intent導向這個例項,而不是建立這個 Activity的新例項。這個 Activity可以被例項化多次,每個例項可以屬於不同的 task,且每個 task可以擁有多個該 Activity的例項 (但是 back stack的棧頂的 activity不是已存在的該 Activity的例項 )。

例如:一個 task的 back stack包括一個根 Activity A和 Activity B,C和在棧頂的 D(當前棧的情況是 A-B-C-D,D在棧頂 )。一個 D的 intent到達了,如果 D是 ”standard”的啟動模式,那麼一個新的例項產生,並且棧會變成 A-B-C-D-D。然而,如果 D的啟動模式是 ”singleTop”的,那麼 intent通過 onNewIntent()傳給已存在的棧頂的 D的例項 ,這時候的棧仍然是 A-B-C-D。如果一個 B的 intent到達了,那麼一個 B的新例項會壓入的棧中,即使 B的啟動模式是 ”singleTop”。

注:當一個 Activity的例項被建立,使用者可以通過 BACK鍵回到前一個 activity。但是如果是一個已存在的例項處理了 Intent,那使用者不能通過 BACK鍵回到 onNewIntent()之前的 Activity狀態了。

“singleTask”

系統建立一個新的任務,並例項化一個新的 Activity物件作為其根。如果已經有一個該 Activity的例項存在在一個單獨的 task中了,系統會將 intent通過 onNewIntent()釋出到已存在的這個例項中去,而不是建立一個新例項。同時只能有一個 Activity的例項存在。

注:儘管該例項存在於任務的根部,但是 BACK鍵仍然返回到之前的 Activity中去。

“singleInstance”

與 ”singleTask”一樣,但是系統不能啟動別個 Activity到這個 Activity的例項所在的 task中去。這個 Activity總是單一的,且是他所在的 task的唯一成員。通過這個 Activity啟動的其它任何 Activity都將單獨啟動一個單獨的 task。



不論一個 Activity是在同一個棧中啟動,還是在一個新的棧中啟動, BACK鍵都會讓使用者回到前一個 Activity中。然而,如果從你的 task(task A)啟動一個被設定為 ”singleTask”啟動模式的 Activity,然後這個 Activity可能有一個例項存在後臺的,這個例項屬於一個 task,並且 有他自己的 back stack(Task B)。在這種情況下, Task B被帶到前臺去處理一個新的 intent,按 BACK鍵在回到 Task A的棧頂 Activity之前,首先會導向 Task B的後臺 Activity。

使用Intent的flag(Using Intent flags)
當啟動一個 Activity的時候,你可以修改 Activity與他的 task的預設的關聯關係,你可以通過向 startActivity()傳遞的 Intent中包含一個 flag來實現。這些你可以使用來改變預設行為的 flag如下:

FLAG_ACTIVITY_NEW_TASK:

在一個新的 task中啟動 Activity。如果你啟動的這個 Activity已在一個 task中運行了,這個 Activity將隨其最後一次儲存的狀態一起被置到前臺,並且這個 Activity會在 onNewIntent()中接到這個請求的 Intent。

這個過程和 ”singleTask”的啟動模式一樣。

FLAG_ACTIVIT_SINGLE_TOP:

如果 Activity啟動的是當前的 Activity(在 Back Stack的棧頂 ),那麼這個存在的實體會接到一個 onNewIntent()的呼叫,而不是建立一個新的例項。

這個過程和 ”singleTop”的啟動模式一樣。

FLAG_ACTIVITY_CLEAR_TOP:

如果一個 Activity已經在執行的棧中啟動了,然後替代啟動一個 Activity新的例項的是,所有的在他上面的 Activity將被銷燬,並且這個 Intent會被傳遞給這個 Activity的例項的 onNewIntent()中。

沒有合適的 lauchMode值與之對應。



處理affinity(Handling affinities)


Affinity表示 Activity更應該屬於哪個 task。預設情況下,同一個應用的所有 Activity有相同的 affinity。所以,預設情況下,同一個應用的所有 Activity更傾向於屬於同一個 task。然而你可以修改 Activity預設的 affinity。不同的應用的 Activity可以擁有共享一個 affinity。同一個應用的 Activity可以分配不同的任務的 affinity。

你可以通過修改 <activity>元素的 taskAffinity屬性修改任何給定的 Activity的 affinity。

TaskAffinity屬性是一個字元值,他必須在 <manifest>被定義成在包名中唯一的 ,因為系統用名字來識別應用的預設的 task affinity。

Affinity在兩種情況下發生作用。

· 當 intent啟動一個 Activity包含一個 FLAG_ACTIVITY_NEW_TASK 的 flag。

一個新的 Activity在預設情況下屬於通過 startActivity()啟動他的那個 Activity所在的 task。它被壓入呼叫者相同的 back stack中。然而,如果給 startActivity()傳遞的 intent包含一個 FLAG_ACTIVITY_NEW_TASK的 flag,系統將會尋找一個不同的 task去安置這個新的 Activity。通常他是一個新 task。然而,它並一是必定是的。如果這裡已存在一個與新的 Activity相同的 affinity的 task, Activity會啟動到這個 task中去。如果沒有存在的,那就開啟一個新的 task。

如果這個 flag促使一個 Activity屬於一個新的 task且使用者是按 HOME鍵離開他的,這裡必須要有辦法讓使用者導回之前的 task。一些實體(像 notification管理者)通常在一個擴充套件的 task中啟動,從不讓他們作為自己的 task的一部分,所以他們常把 FLAG_ACTIVITY_NEW_TASK放到 Intent中傳給 startActivity()。如果你有一個 Activity被一個外部實體啟用,而且這個啟用可能會使用到這個 flag,那麼你要注意,使用者有一個獨特的方式返回到啟動他的 task中去,比如通過一個 lancher的圖示。

· 當 Activity的 allowTaskReparenting屬性被設為 true時。

在這種情況下,那個 Activity可以從他啟動的 task移動到他的 affinity的 task中去,當這個 task轉到前臺的時候。

例如:假如有一個旅遊的應用,他包含一個報告選擇了的城市的天氣狀況的 Activity。他有一個與同一應用的其它 Activity有相同的 affinity(預設的系統的 affinity)並且他允許用這個屬性 re-prearenting。當你的一個 Activity啟動了這個天氣報告的 Activity,他初始的屬於與你的 Activity相同的 task。然而,當這個旅遊應用的 task轉到了前臺,天氣報告的 Activity被移到那個 task並且顯示他。



清理back stack(Clearing the back stack)
如果使用者離開一個 task較長的時間,系統清理掉除 root Activity之外的其它所有 Activity。當用戶返回這個 task,只有 root Activity被恢復。系統的這麼做是因為,經過一個相當長的時間,使用者可能是放棄了之前的工作,而現在返回這個 task是為了開啟某項新的工作。

修改這個行為你可以使用如下一些 Activity的屬性:

alwaysRetainTaskState

如果一個 root activity的這個屬性被設為 true,那麼上面描述到的預設的行為將不會發生。 Task將在棧中保留所有的 Activity,即使過了較長一段時間。

clearTaskOnLaunch

如果 task的 root activity的這個屬性被設為 true, 不論什麼時候,使用者離開這個 task,然後回到他,這個棧會清理到 root activity。換句話說,他是 alwaysRetainTaskState的反義詞。使用者總是返回到這個棧的初始化狀態,即使是剛剛離開這個棧。

finishOnTaskLaunch

這個屬性像 clearTaskOnLaunch一樣,但是他僅針對一個單獨的 Activity,而不是整個 Task。他也可以促使任何一個 Activity離開,包括 root Activity。當他設為 true時,這個 activity只會當前會話儲存部分 task。當用戶離開再返回這個 task,他將不再重現。

啟動一個task(Starting a task)
你可以設定一個 Activity為一個 task的入口,其方法是給出一個 intent filter 包括一個 ”android.intent.action.Main”作為其特別的 action和一個 ”android.intent.category.LAUNCHER”作為特別的 category。例如:

<activity ... >
<intent-filter ... >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>

這種型別的 intent filter為 activity產生一個圖示和一個標籤,他們將顯示在應用啟動器中,他給使用者了一個啟動這個 activity的方法,以及當其啟動之後任何時候回到這個任務的途徑。

第二個能力很重要,必須讓使用者可以離開一個任務,並且可以通過 Activity launcher返回來。為些,那兩個啟動模式通常用來初始化一個任務,,僅當這個 Activity擁有一個 ACTION_MAIN和一個 CATEGORY_LAUNCHER的 filter時才使用 ”singleTask”或 ”singleInstance”。想象一下,假如,如果沒有這個 filter將有可能發生什麼 ?一個 intent啟動了一個 ”singleTask”的 Activity,初始化為一個新的 task,使用者在這個任務上消磨了一些時間。然後使用者按 HOME鍵,這時這個任務被髮到後臺,不可見了。由於 其在 application 的 launcher中沒有一個 描述,使用者就沒有辦法回到這個 task了。

這種情況下,如果你不希望使用者回到某個 Activity,可以設定 <Activity>元素的 finishOnTaskLaunch為 true。