Android Api Component---翻譯任務和回退棧(Tasks and Back Stack),androidstack
一個應用程式通常包含多個activity。每一個activity應當圍繞一個指定的使用者可以執行的並且可以開啟其它activity的動作種類被設計。例如,一個emali應用程式也許有一個activity展示新訊息列表。當使用者選擇了一個訊息的時候,一個新的activity會開啟查看這個訊息。
一個activity甚至可以開啟裝置上的其它應用程式的activity。例如,如果你的應用程式向發送一個郵件訊息,你可以定義一個intent執行一個"send"動作並且包含一些像email地址和訊息的資料。來自於另一個應用程式的activity定義他自己使用這種intent開啟。在這這個例子中,這個intent將發送一個email,因此一個email應用程式的"compose"activity開啟(如果多個activity支援相同的intent,那麼系統會讓使用者選擇使用哪一個)。當email發送之後,你的activity恢複並且它就好像這個郵件activity是你的應用程式的一部分。儘管應用程式之間的activity是不同的,但是Android通過保持所有的activity在相同的task中無縫的維護這些使用者體驗。
一個任務是當執行某種工作時與使用者互動的activity的集合。activity被安排在一個棧中(回退棧),每一個activity會按照進入的順序被開啟。
裝置的Home螢幕是大多數任務開始的地方。當使用者在一個應用程式啟動器裡按了一個表徵圖時,應用程式的任務就被調到前台。如果給這個應用程式沒有任務存在(應用程式最近已經被使用了),那麼一個新的任務會被建立並且"main"activity在那個棧中給那個應用程式以根activity開啟。
噹噹前的activity開啟另一個的時候,新的activity被推到棧頂並且取得焦點。前面的activity任然在戰中,但是被停止了。當一個activity停止的時候,系統回收當前的使用者介面的狀態。當使用者按了返回按鈕,當前的activity會從棧頂被彈出(這個activity被銷毀)並且前面的activity恢複(前面的UI的狀態被恢複)。在棧中的activity使用者不會被重新整理,只有從棧中推送和彈出-當在當前activity中開啟的時候推送到棧中並且當使用者使用返回按鈕離開的時候被彈出。像這樣,回退棧操作作為一個“後進先出”的對象結構。用時間軸展示了用當前的回退棧處理activity之間準時的每一個點的行為。
這個圖代表了每個新的activity在任務中如何添加項到後台棧中。當使用者按了回退按鈕的時候,當前的activity被銷毀並且前一個activity重新開始。
如果使用者繼續按返回,在棧中的每一個activity將被彈出去展現前一個activity,直到使用者返回到主畫面未知(或者當任務開始的時候無論哪一個正在啟動並執行activity)。當所有的activity從棧中被移除的時候,這個任務也就不再存在。
當使用者開始一個新任務或者通過Home鍵進入主畫面的時候,一個任務是一個能夠移動到“後台”的連續單元。然後在後台,在任務中的所有activity都會被停止,但是對這個任務的回退棧仍然是完整的-指示當另一個任務進行的時候,這個任務只是失去了焦點,正如展示的一樣。一個任務然後可以返回到“前端”以至於使用者可以獲得他們離開時的位置。加入,例如,當前的任務(Task A)在它的棧中有三個activity-兩個在當前activity的下面。使用者按了Home鍵,然後從這個應用程式啟動器開啟一個新的應用程式。當主畫面出現的時候,Task A進入後台。當新應用程式開始的時候,系統給這個應用程式(Task B)用它擁有的activity的棧開始了一個任務。在跟那個應用程式互動之後,使用者又返回Home並且選擇了那個原始的被開啟的Task A。現在,Task A來到了前端-在它的棧中的所有三個activity是完整的並且在棧頂的activity重新開始。此時,使用者可以通過進入Home轉換返回到Task B並且選擇那個被開始的任務的應用程式圖示(或者通過從掃視畫面選擇app的任務)。這是Android上的一個多任務的例子。
注意:多任務能在後台被立即操作。然而,如果使用者同時運行著很多背景工作,系統也許會按照回收記憶體的順序銷毀後台activity,這樣就導致那些activity的狀態將丟失。關於Activity state看下面的段落。
由於在回退棧中的activity永遠不會被重新整理,如果你的應用程式允許使用者開始超過一個的一個特別的activity,那個activity的執行個體被建立並且被推送的棧中(而不是帶入任何activity執行個體的上面)。像這樣的,在你的應用程式中的一個activity也許被初始化多次(甚至來自於不同的任務),就像在被展示的一樣。像這樣的情況,如果使用者使用返回按鈕導航返回,每一個activity的執行個體按照他們被開啟的順序顯示(每一個用他們自己的UI狀態)。然而,如果你不想讓一個activity被初始化多次,你可以修改這種行為。如何這樣做,將會在下面的Managing Tasks段落中被討論。
來給activity和task的行為做一個總結:
當Activity A開啟Activity B的時候,Activity A被停止,但是系統保留著它的狀態(像滾動位置和輸入表單的文本)。如果使用者在Activity B中按了返回按鈕,Activity A用它的狀態重新開始被恢複。
當使用者通過按Home鍵離開任務的時候,當前的activity被停止並且它的任務進入後台。系統保留著每個在任務中的activity的狀態。如果使用者後來通過選擇啟動器表徵圖開始任務重新開始了這個任務,那麼這個任務會進入前端並且在棧頂重新開始這個activity。
如果使用者按了返回按鈕,當前的activity從棧中被彈出並且被銷毀。在棧中的前面的activity被重新開始。當一個activity被銷毀的時候,系統不保留activity的狀態。
activity可以被初始化多次,甚至從其它任務中初始化。
導航設計
對於app在android上如果導航工作的,讀Android Design's Navigation文檔
管理Task
在上面被描述的Android管理工作和回退棧的這種方式,通過把所有的被開始的activity放在相同的任務中並且用“後進先出”棧-對於大多數的應用程式起很多的作用並且你不需要擔心你的activity如何跟任務關聯或者他們在回退棧中是如何存在的。然而,你可能決定你想要終端正常的行為。也許你想讓你的應用程式中的activity被開始的時候(代替放在當前任務內)開始一個新的任務;或者當你開始一個activity的時候,你想讓一個存在的activity的執行個體走上前(代替在棧頂建立一個新的執行個體);或者當使用者離開任務的時候你想讓你的回退棧清除所有的activity,除了根activity。
你可是使用在<activity>清單元素中使用這個屬性做那些事情或者更多事情,並且帶著你要傳遞給startActivity()中的intent的flag。
就這點而言,主要的<activity>屬性你可以使用的是:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
還有主要的intent的flag你可以使用的是:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
在下面的段落中你將看到你如何使用那些清單屬性和intent的flag來定義activity如何跟task關聯以及他們在回退棧中如何運行。
還有,分開討論activity和task是考慮到task和activity也許被描述或者被管理在那個綜述螢幕中。看更多Overview Screen的資訊。通常你應該允許系統定義你的task和activity在綜合螢幕中被描述,並且你不需要修改這種行為。
注意:大多數應用程式不會中端給activity和task的預設的行為。如果你決定你的activity需要就該預設行為,使用警告並且確保在運行期測試以及從其它的activity和任務使用返回按鈕導航回退到它的時候activity的可用性。確保測試導航行為與使用者的期望行為的衝突。
定義啟動模式
啟動模式允許你定義一個新的activity的執行個體如果跟當前任務關聯起來。你可以用兩種方式定義不同的啟動模式:
使用清單列表
當你在你的manifest檔案中定義了一個activity的時候,你當它開始的時候你可以指定這個activity如何跟任務關聯。
使用Intent的flag
當你調用startActivity()的時候,你應該在Intent中包含一個flag,這個flag定義了這個新的activity如何(或者是否)應該和當前任務關聯。
像這樣的,如果Activity A開啟Activity B,Activity B可以在它的manifest中定義它應該如何跟當前的task(或者所有)關聯,並且Activity A也可以要求Activity B應該如何跟當前任務關聯。如果兩個activity都定義了Activity B如何跟一個task關聯,那麼Activity A的需求(在intent中被定義的)遵守於Activity B的需求(在它的manifest中被定義)。
注意:一些有效啟動模式對於這個manifest檔案的intent的flag是無效的,同樣的,一些給intent的flag的有效啟動模式不能在manifest中被定義。
使用manifest檔案
當在你的manifest檔案中定義了一個activity的時候,你可以指定這個activity使用<activity>元素的lanuchMode屬性應該如何跟一個任務關聯。
這個lanuchMode屬性指定了一個關於這個activity應當如何進入一個任務被啟動並執行指令。有四個不同的啟動模式你可以分配給launchMode屬性:
"standard" (預設模式)
預設的,系統在任務中建立一個activity的執行個體,這個activity從這個任務中被開啟並且把intent路由給這個activity。這個activity能被初始化多次,每一個執行個體可能屬於不同的任務,而且一個任務可能有多個執行個體。
"singleTop"
如果一個activity的執行個體已經存在於當前任務的頂部,那麼系統通過調用給它的onNewIntent()把這個intent路由到那個執行個體,然不是建立一個activity的新執行個體。這個activity可能會被初始化多次,每一個執行個體可能屬於不同的任務,並且一個任務可能有多個執行個體(但是如果這個activity在回退棧的頂部,那就只有一個activity的執行個體存在)。
例如,加入一個任務的後台棧用activity B,C和D在頂端組成了根activity A。一個給activity D的intent到達。如果D有預設的"standard"啟動模式,一個新的類執行個體就被啟動並且這個棧會變成A-B-C-D-D。然而,如果D的啟動模式是"singleTop",那麼D重已經存在的執行個體通過onNewIntent()接收這個intent,因為他在棧的頂部-這個棧任然是A-B-C-D。但是,如果有個給activity B的intent到達,那麼一個B的執行個體將被添加到這個棧中,甚至如果它的啟動模式是"singleTop"。
注意:當一個activity執行個體被建立的時候,使用者可能案子啊回退按鈕返回到前一個activity。但是當一個activity的執行個體已經存在的時候運用一個新的intent,在新的intent到達onNewIntent()裡面之前,使用者不能按返回按鈕來回到上一個activity的狀態。
"singleTask"
系統建立一個新任務並且在新任務的根部初始化這個activity。然而,如果這個activity的執行個體已經存在於一個分離的任務中,系統就通過調用給它的onNewIntent()方法路由intent到已經存在的activity的執行個體中,然不是建立一個新的執行個體。在同一時間,只有一個activity的執行個體可以存在。
注意:儘管activity開始在一個新的任務中,但是回退按鈕仍然可以給使用者返回到前一個activity。
"singleInstance"
除了系統不啟動任何其它的activity進入這個持有這個執行個體的任務中,其它的跟"singleTask"一樣。這個activity總是單個的,並且它的任務只有一個成員;任被開啟的activity通過這一個分割的任務開啟。
作為另一個例子,Android瀏覽器應用程式通過在<activity>元素中指定singleTask啟動模式定義web瀏覽器應該總是在它自己的任務中開啟。這就意味著,如果你的應用程式發布了一個開啟Android Browser的intent,它的activity不會被放在跟你的應用程式相同的任務中。相反,要麼開始一個新的任務來開啟瀏覽器,如果瀏覽器已經背景工作中運行,那個任務會被帶到前面來操作這個新的intent。
不管是否一個activity開啟在新任務或者相同任務中,就好像是這個activity開啟了它,回退按鈕總是將使用者帶到前一個activity。然而,如果你開始一個指定了singleTask啟動模式的activity,那麼如果一個activity的執行個體存在於一個背景工作中,整個任務會被帶到前面。此時,後台棧在它的頂部現在包含了所有來自於被帶到前面的這個任務的activity。如:
關於在manifest檔案中更多使用啟動模式的資訊,看<activity>元素文檔,在哪裡lanuchMode屬性和被接收的值被討論更多。
注意:你給你的activity用launchMode屬性指定的行為可以被Intent所帶的flags覆蓋開啟你的activity,接下來會討論這個。
使用Intent的flag
當你開始一個activity的時候,你可能通過包含在你傳遞給startActivity()的intent中的flag修改了一個activity的預設的關聯給它的任務。你能用這些flag修改預設行為的是:
FLAG_ACTIVITY_NEW_TASK
在一個新的任務中開啟activity。如果一個任務已經給你現在正在開啟的activity運行著,那麼那個任務會用它的最後的狀態被恢複來帶到前端並且這個activity在onNewIntent()中接收一個新的intent。
這個產生的行為跟在前面討論過的"singleTask"launchMode值是相似的。
FLAG_ACTIVITY_SINGLE_TOP
如果被開啟的activity是當前activity(在回退棧頂部),那麼這個已經存在的執行個體代替建立一個新的activity執行個體接收一個調用到onNewIntent()。
這個跟前面討論的"singleTop"launchMode值產生相同的行為。
FLAG_ACTIVITY_CLEAR_TOP
如果被啟動的activity已經運行在當前任務中,那麼代替運行一個activity的新執行個體,在這個activity的上面的所有其它activity被銷毀並且這個通過onNewIntent()給這個被重新開始的activity的執行個體傳遞了這個intent。
沒有launchMode屬性值產生這種行為。
FLAG_ACTIVITY_CLEAR_TOP是最長跟FLAG_ACTIVITY_NEW_TASK結合使用。當一起唄使用的時候,那些flag事一種本地化一個已經在其它認為中存在的activity的方式並且把它放在一個能響應intent的地方。
注意:如果這個activity的指定的啟動模式是"standard",那它也會從這個棧中被移除並且在它的位置一個新的執行個體被啟動來操作進入的intent。那是因為當啟動模式是"standard"的時候,一個新執行個體總是為了一個新的intent被建立。
處理關係(Handling affinities)
這個關係指明一個activity更喜歡屬於哪一個task。預設的,來自於相同應用程式的所有activity彼此有一個關係。因此,預設的,在相同應用程式中的所有的activity更喜歡在相同的任務中。然而,你可以給activity修改預設關係。被定義在不同應用程式中的activity可以共用一個關係,或者被定義在相同應用程式中的activity可以被安排在不同的任務關係中。
你可以使用<activity>元素中的taskAffinity屬性來修改被給定的這個activity的關係。
這個taskAffinity屬性取一個字串值,在<manifest>元素中定義的這個屬性它必須在預設的包名中是唯一的,因為系統給應用程式使那個名字來分辨預設的任務關係。
在兩種環境下這個關係會開始起作用:
當包含這個FLAG_ACTIVITY_NEW_TASK標記的intent調用startActivity()啟動一個activity的時候。你一個新的activity預設的被運行在activity的任務中。它作為調用者被推到相同的後台棧中。然而,如果被傳遞給startActivity()的intent包含FLAG_ACTIVITY_NEW_TASK標記,系統尋找一個不同的任務來給新的activity空間。通常,它是個新的任務。然而,它不需要是。如果作為新的activity在已經存在的任務中有了相同關係的activity,這個activity被啟動到這個任務中。如果沒有,它會開始一個新任務。
如果這個標記導致一個activity開始一個新的任務並且使用者按Home鍵離開它,那就必須要有一些方式可以給使用者導航回到那個任務。一些實體(像通知管理)總是在一個內部任務中開啟activity,從來不會作為他們擁有的部分,因此他們總是把FLAG_ACTIVITY_NEW_TASK放入它們傳遞給startActivity()的intent中。如果你有一個activity可以通過內部實體調用也許就是用了這個標記,注意使用者有一個獨立的方式可以回到他們開啟的那個任務,像用啟動器表徵圖(任務的根activity有一個CATEGOR_LAUNCHER的intent過濾器;看下面的Starging a task段落)。
當一個activity把它的allowTaskReparenting屬性設定為"true"。
在這個例子中,當任務來到前端的時候,這個activity可以從這個開啟它的有一個關係的任務中移除。
例如,假設在被選擇的城市的一個天氣預報的activity狀態被定義為旅遊應用程式的部分。它跟其它的那些在相同應用程式中的activity有相同的關係(預設應用程式的關係)並且它允許父類使用這個屬性。當你的其中的一個activity開啟了這個天氣預報的activity的時候,它最初跟你的activity屬於相同的任務。但是,當旅遊應用程式的任務回到前台的時候,天氣預報activity被安排到那個任務中並且在它裡面被陳列。
建議:如果一個.apk檔案從你使用者的角度來看包含超過一個"application",你可能想要使用taskAffinity屬性分配不同的關係給那些跟每一個"application"關聯的activity。
清除回退棧
如果使用者離開一個任務很長時間,系統會把任務中的除了根activity以外的所有activity清除掉。當使用者又返回到任務的時候,只有根activity被恢複。系統資料表現的這種方式,因為,很長時間以後,使用者很可能有已經遺棄了它們之前正在做的和返回到任務開始新的事情。
你可以用activity的一些屬性來修改這種行為:
alwaysRetainTaskState
如果這個屬性在任務的根activity中被設定為"true",被描述的預設行為就不會發生了。這個任務回收在它的任務中的所有activity甚至在一段時間之後。
clearTaskOnLaunch
如果這個屬性在一個任務的根activity中被設定為"true",無論使用者什麼時候離開並且返回,棧都會被清理到根activity。換句話說,它是alwaysRetainTaskState的對立面。使用者總是返回到任務的初始狀態,甚至在離開了任務很長時間之後。
finishOnTaskLaunch
這個屬性像clearTaskOnLaunch,但是它操作單個的activity,而不是整個任務。它也可以引起任何activity離開,包括根activity。當它被設定為"true",這個activity在當前session下任然是這個任務的一部分。如果使用者離開了並且又回到這個任務,它不再呈現。
開始一個任務
你可以設定一個activity作為一個任務的進入點,通過給它一個intent帶有"android.intent.action.MAIN"的過濾器當做被指定的動作還有"android.intent.category.LAUNCHER"當做被指定的類別。例如:
<activity ...> <intent-filter ...> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter></activity>
一個這種intent的過濾器目標是一個給activity的icon和標籤將在應用程式啟動器中被陳列,給使用者一種啟動這個activity的和返回到這個它被啟動之後的任何時間建立任務的方式。
這個的第二個功能是重要的:使用者必須能離開任務並且然後後來使用這個activity的啟動器回來。對於這種原因,這兩個啟動模式標記activity好像總是初始化一個任務,"singleTask"和"singleInstance",只有當activity有一個ACTION_MAIN和一個CATEGORY_LAUNCHER過濾器的時候才應該被使用。想象一下,例如,如果過濾器丟失了可能會發生什麼:一個intent啟動一個"singleTask"activity,初始化一個新任務,並且使用者花費一些時間在那個任務中工作。使用者然後按了Home鍵。任務現在被發送到後台並且不可見了。現在使用者沒有方式回到這個任務,因為它在這個應用程式啟動器中沒有被描述。
對於那些你不想讓使用者能回到一個activity的情況,設定<activity>的元素的finishOnTaskLaunch為"true"(看 Clearing the stack)。