Android 復習_Task 與 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。