andorid Tasks and Back Stack

原文地址:http://developer.android.com/guide/components/tasks-and-back-stack.html

 轉載自:http://blog.csdn.net/ff20081528/article/details/17219951

       一個應用往往包含很多activities.每個activity應圍繞着用戶可執行的特定動作來設計,並且可以啓動其它activities.例如,一個email應用可能有一個顯示新郵件列表的activity.當用戶選擇一個郵件,一個新的activity被打開以顯示郵件內容.

  一個activity也可以打開同一設備上存在於其它應用的activities。例如,如果你的應用想要發送一個郵件,你可以定義一個intent來執行一個"send"動作幷包含一些數據,比如一個地址和一條信息.另一個應用中的activity可以自己處理這種intent的然後被打開(如果有多個activitie支持同樣的intent,那麼系統會讓用戶選擇一個).當email被髮送後,你的activity被恢復並且看起來發送郵件的activity像是你的應用的一部分.即使那些activities可能來自不同的應用,Android靠着把兩個activity保存在同一個任務中來實現這種無縫的用戶體驗.

  一個任務是用戶在執行某種工作時所需要的activities的集合.activities放置在一個棧("back stack")中,按照打開的順序排列.

  設備的Home屏是大多數任務的開始場所.當用戶觸摸在應用啓動臺中的圖標(或一個home屏上的快捷方式)時,應用的任務就來到了前臺.如果沒有這個應用已存在的任務(這個應用最近沒有被使用),那麼一個新的任務被創建並且這個應用的"main"activity被作爲棧的根activity打開.

  當前的activity啓動了另一個activity,新的activity被放置在棧頂並擁有焦點.先前的activity依然保存在棧中,但是停止了.當一個activity停止時,系統保存了它的用戶界面的當前狀態.當用戶點擊後退按鈕時,當前的activity被從棧頂彈出(activity被銷燬了)並且先前的activity被恢復了.棧中的activities永不會被重新排列,只會進行入棧或出棧行爲當被當前activity啓動時就入棧,當用戶使用後退按鈕離開它時就出棧.如此,後退棧也是一個後進先出的棧.

 

下圖按照時間軸展示了activities在stack中的行爲變化。


        圖1,展示了一個task中的一個新的activity作爲一個item加入到back stack中。當用戶按了返回按鈕時,當前的activity將被銷燬,前一個activity將復活。

  如果用戶繼續後退,那麼棧中的每個activity被彈出來然後展示上一個,直到用戶退到Home(或到達任務開始時運行的那個activity).當所有的activities都從棧種移除,任務就不再存在.

  一個任務是一個有聚合力的單元,它可以在用戶啓動一個新的任務或回到home屏時被整體地移到後臺.當位於後臺時,任務中的所有的activities都處於停止,但是任務的後退棧卻保存完整—當任務被另一個任務取代時,僅僅是失去了焦點.見圖2:


2. 兩個任務:任務B到了前臺,任務A於是被打入後臺,伺機恢復.

 

  一個任務可以再回到前臺,於是用戶可以獲得他離開時的模樣.舉個例子,當前的任務(任務A)有三個activities在其棧中—兩個在下面.用戶按下Home 按鈕,然後又啓動一個新的應用.當Home屏出現時,任務A到了後臺.當新應用啓動時,系統爲這個應用開始了一個任務(任務B).當使用完新應用時,用戶再次回到了Home屏然後選擇了啓動任務A的那個應用.現在,任務A來到了前臺棧中所有的三個activities都完整保留並且位於頂層的activity被恢復.此時,用戶也可以再回到home屏然後選擇任務B的應用於是回到任務B(或通過長按Home 按鈕以顯示最近的任務然後選擇它)

注:多個任務可以同時存在於後臺.然而,如果用戶在同一時刻運行多個後臺任務,系統可能會銷燬後臺activities來釋放內存,從而導致activity狀態的丟失.

  因爲後退棧中的activities從不會被重排,如果你的應用允許用戶從不只一個activity啓動一個特殊的activity,一個新的activity的實例會被創建並壓入棧中(而不是把這個activity的當前實例弄到前臺來).所以,你的應用中的一個activity可能被多次實例化(甚至是從不同的任務),如圖3所示.同樣的,如果用戶使用後退按鈕向後導航,activity的每個實例都會按照打開的順序重新顯現(每個都保持它們自己的狀態).然後,你如果不想某個activity被實例化多次,你可以改變這種行爲.後面會講到如何做.

 

 

3.一個activity被實例化多次.


下面總結一下下activity和任務的默認行爲:

  • ActivityA啓動ActivityBActivityA停止,但是系統保存它的狀態(比如滾動條的位置和表單中輸入的文本).如果用戶在Activity B中按下了後退按鈕,ActivityA以保存的狀態恢復.

  • 當用戶按下Home按鈕離開了一個任務,當前的activity停止同時它的任務進入後臺.系統保持任務中每個activity的狀態.如果用戶後來運行了這個任務的應用而恢復了這個任務,任務回到前臺並使棧頂端的activity恢復.

  • 如果用戶按下了後退按鈕,當前的activity從棧中彈出並被銷燬.前一個activity被恢復.當一個activity被銷燬時,系統不再保持activity的狀態.

  • Activitie可以被多次實例化,即使是從另外的任務.

保存Activity的狀態

  如上所述,系統默認下會在activity停止的時候保存其狀態.如此一來,當用戶導航到前一個activity時,其用戶介面顯示得跟離開時一樣.然後,你應該使用activity的回調方法們保持它的狀態,因爲activity可能會被銷燬然後被重新創建.當系統停止了你的一個activities(比如當新的activity啓動或任務被移到後臺),系統可能爲了釋放內存會完全銷燬那個activity.當這事發生時,activity的狀態丟失.如果真發生了這種現象,系統依然知道那個activity在後退棧中佔有一個位置,但是當activity被弄到前臺時,系統必須重新創建它(而不是僅僅恢復它).爲了避免丟掉用戶的工作,你應該通過實現activityonSaveInstanceState()來提前保存狀態.

  關於保存activitystate的更多知識,請看 Activities 
 

管理tasks

  Android管理任務和後退棧的方式,如前面文章所述—通過把所有接連啓動的activity放在同一個任務中並且是同一個後進先出的棧中—在大多數應用中工作得很好並且你無需關心你的activity如何與任務相關連或如何在後退棧中存在.然而,你可能決定要打破這種正常的行爲.可能你想在你應用的activity啓動時開始一個新的任務(而不是放置到當前棧中);或者,當你啓動一個activity,你想把已經運行的它的一個實例提到前臺來(而不是創建一個新的實例放在後退棧的頂端);或者,你希望當用戶離開任務時,你的後退棧清除了根activity以外的所有activity

  可以做這些事情,甚至更多事情,通過設置manifest<activity>的屬性和傳到startActivity()intentflag

在這一點上,你可以設置的最重要的<activity>屬性有:

taskAffinity

launchMode

allowTaskReparenting

clearTaskOnLaunch

alwaysRetainTaskState

finishOnTaskLaunch



可以使用的最重要的intent flag

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_CLEAR_TOP

FLAG_ACTIVITY_SINGLE_TOP


注意:大多數應用不應改變activity和任務的默認行爲.如果你確定必須要改變默認行爲,你必需小心並且保證測試了activity在啓動時和後退導航到它時的可用性.確保測試了與用戶習慣相沖突的導航行爲.

 

定義啓動模式

  啓動模式使你可以定義新的activity如何與當前的任務相關聯.有兩種方法來定義不同的啓動模式:

  • 使用manifest文件

      當你在你的manifest文件中聲明一個activity時,你可以指定activity在啓動時如何與任務相關聯.

  • 使用Intentflag

      當你調用startActivity()時,你可以在Intent中包含指明新的activity如何(或是否)與當前棧關聯的flag

  同樣的,如果ActivityA啓動ActivityBActivityB可以在它的manifest中定義如何與當前的任務關聯(如果真的存在)並且ActivityA也可以請求讓ActivityB如何與當前的任務關聯.如果兩個activity都定義了ActivityB如何與任務關聯,那麼ActivityA的請求(intent中定義)優先於ActivityB的請求(在它的manifest中定義)



注:一些啓動模式可以用在manifest中但不能用在intentflag上,同樣的,一些啓動模式可以用在intentflag上但不能用在manifest中.

使用manifest文件

  當在你的manifest文件中聲明一個activity時,你可以使用<activity>元素的launchMode屬性指定activity如何與一個任務關聯.

  launchMode屬性指明瞭activity如何啓動到一個任務中去.有四種不同的啓動模式你可以用於指定給launchMode屬性:

  • "standard"(默認模式)

      默認.系統創建一個新的activity的實例到啓動它的任務中.activity可以被多次實例化,每個實例可以屬於不同的任務,也可以屬於同一個任務.

  • "singleTop"

      如果一個activity的實例已經存在於當前任務的棧的頂端,系統通過調用它的onNewIntent()方法把intent路由到這個實例,而不是創建一個新的實例.activity可以被多次實例化,每個實例可以屬於不同的任務,並且一個任務可以具有多個實例(但只是當位於後退棧的頂端的activity不存在時纔會出現這種現像)

      例如,假設一個任務的後退棧中有根ActivityAactivityB,C,(A-B-C-D;D位於頂端).一個intent到達了D類型的activity(不是指這裏的acitivityD).如果D具有默認的"standard"啓動模式,一個新的類的實例被啓動並且棧變爲A-B-C-D-D.然而,如果D的啓動模式是"singleTop",那麼這個已存在的ActivityD就通過onNewIntent()接收到intent,因爲它在棧的頂端棧於是依然保持A-B-C-D.又然而,如果一個intent到達了B類型的activity(不是此處的activityB),那麼一個新的B實例被添加到棧中,即使它的啓動模式是"singleTop"

      注:當一個新的activity的實例被創建,用戶可以按下後退鍵回到上一個activity.但當一個已存在的activity實例處理了一個新intent,用戶就不能按下後退鍵回到當前的activityintent來之前的狀態.

  • "singleTask"

      系統創建一個新的任務並且實例化activity爲新任務的根.然而,如果一個activity的實例已存在於另一個任務,系統就會通過調用這個activityonNewIntent()intent路由給它,而不是創建一個新的實例.某個時刻只有一個activity的實例可以存在.

      注:儘管activity在一個新任務中啓動,後退鍵依然可以返回到上一個activity

  • "singleInstance".

      跟"singleTask"一樣.除了系統不能再啓動其它activity到擁有這個activity實例的任務中.activity永遠是任務的唯一;任何由這個activity啓動的其它activity都在另一個任務中打開.

       舉一個例子,Android瀏覽器應用聲明網頁瀏覽activity必須在它自己的任務中打開通過在<activity>元素中指定singleTask啓動模式.這表示如果你的應用發出一個intent來打開Android瀏覽器,它的activity不會放到你的應用所在的任務中.代替的是,可能一個新的任務爲瀏覽器啓動,或者,如果瀏覽器已經運行於後臺,它所在的任務就被弄到前臺並接受這個intent

  不論一個從一個新任務啓動activity還是在一個已存在這種activity的任務中啓動,後退鍵總是能後退到前一個activity.然而,如果你在任務A中啓動一個聲明爲singleTask模式的activity,而這個activity可能在後臺已有一個屬於一個任務(任務B)的實例.任務B於是被弄到前臺並處理這個新的intent.那麼此時後退鍵會先一層層返回任務BActivity,然後再返回到任務A的頂端activity4演示了這種情形.

4.演示一個"singleTask"啓動模式的acitvity如何被添加到一個後退棧中.如果這個activity已經是一個後臺任務(任務B)自己的棧的一部分,那麼整個後退棧被弄到前臺,位於當前任務(任務A)的上面.

 

使用 Intentflags

  當啓動一個activity時,你可以在給startActivity()intent中包含flag以改變activity與任務的默認關聯方式.你可以用來改變默認行爲的flag有:


  • FLAG_ACTIVITY_NEW_TASK

      在新的任務中啓動activity-即不在本任務中啓動.如果一個包含這個activity的任務已經在運行,那個任務就被弄到前臺並恢復其UI狀態,然後這個已存在的activityonNewIntent()中接收新的intent

      這個標誌產生與"singleTask"相同的行爲.

  • FLAG_ACTIVITY_SINGLE_TOP

      如果正啓動的activity就是當前的activity(位於後退棧的頂端),那麼這個已存在的實例就接收到onNewIntent()的調用,而不是創建一個新的實例.

      這產生與"singleTop"模式相同的行爲.

  • FLAG_ACTIVITY_CLEAR_TOP

      如果要啓動的activity已經在當前任務中運行,那麼在它之上的所有其它的activity都被銷燬掉,然後這個activity被恢復,而且通過onNewIntent()initent被髮送到這個activity(現在位於頂部了)

      沒有launchMode屬性值對應這種行爲.

      FLAG_ACTIVITY_CLEAR_TOP多數時候與FLAG_ACTIVITY_NEW_TASK聯用.當一起使用時,會在其它任務中尋找一個已存在的activity實例並其把它放到一個可以響應intent的位置.

    注:如果Activity的啓動模式是"standard"FLAG_ACTIVITY_CLEAR_TOP會導致已存在的activity被從棧中移除然後在這個位置創建一個新的實例來處理到來的intent.這是因爲"standard"模式會導致總是爲新的intent創建新的實例.

處理任務親和力

  親和力表明了一個activity"心儀"哪個任務.默認下,屬於同一個應用的所有activities之間具有相同的任務親和力.所以,默認下,一個應用的所有activities首選屬於同一任務.然而,你可以修改一個activity的默認任務親和力.定義於不同應用的Activities可以具有相同的任務親和力,或者同一應用中的activities可以分配不同的任務親和力.

  你可以使用<activity>元素的taskAffinity屬性來修改一個activity的任務親和力.taskAffinity屬性使用字符串作爲值,這個字符串必須與在<manifest>中聲明的默認包名不同,因爲系統使用包名來標識默認的任務親和力.

  親和力在以下兩種情況起作用:

  • 當啓動一個activityintent包含FLAG_ACTIVITY_NEW_TASK標誌.

      一個新的activity默認是在調用startActivity()activity所在的任務中安置.然而,如果傳給startActivity()intent包含了FLAG_ACTIVITY_NEW_TASK標誌,系統就會查找另一個能安置這個新activity的任務.通常,它會是一個新任務.然而但是,並不是必須這樣做.如果有一個已存在的任務具有與新activity相同的親和力,那麼這個activity就被啓動並安置到這個已存在的任務中.如果沒有這樣的任務,纔開始一個新的任務.

      如果這個標誌導致了一個activity在一個新的任務中啓動然後用戶按下了HOME鍵離開了這個新任務,那麼必須有一些方法使得用戶可以重新回到這個任務.一些實體(比如通知管理器)總是在一個另外的任務中啓動activity而從不作爲自己任務的一部分,於是它總是把FLAG_ACTIVITY_NEW_TASK設置到傳給startActivity()intents中.如果你有一個activity可以被外部實體使用這個標誌調用,應小心用戶可能用一個獨立的方法回到這個啓動的任務,比如使用啓動圖標(任務的根activity有一個CATEGORY_LAUNCHERintent過濾器).-翻譯得挺難受,這句話也就是說,只要使用了相同的親和力,用戶就能回到這個已啓動的任務中.

  • 當一個activityallowTaskReparenting屬性爲"true"時.

      在此情況下,activity可以從啓動它的任務移動到一個親和的任務中,當後一個任務來到前臺時.

      例如,假設一個報告所選城市的天氣狀況的activity是作爲一個旅遊應用的一部分.它與同一個應用中的其它activity具有相同的親和力(默認的application親合力)並且它被允許重認父母.當你的一個activity啓動了這個天氣預報activity,它起初是與你的actvity屬於同一個任務.然而,當旅遊應用的任務進入前臺時,天氣預報activity就被重新分配到這個任務並在其只顯示.

小提示::如果一個.apk文件包含多個從用戶角度所認爲的"應用",你可能想通過爲activity指定屬性taskAffinity來使它們連接到不同的"應用"



清空後退棧

  如果用戶離開了一個任務很長一段時間,系統會清空任務中除了根activity之外的所有其它activity.當用戶重新返回這個任務時,只有根activity被恢復.系統之所以這樣做,是因爲經過一大段時間之後,用戶很可能已拋棄掉他們已經做的並且回到任務開始做一些新的事情.

  有一些activity屬性你可以用來改變這種行爲:

  • alwaysRetainTaskState

      如果任務的根activity的這個屬性被設置爲"true",前面所述的默認行爲就不會發生.任務保持所有的後退棧中的activity,即使經過很長一段時間.

  • ClearTaskOnLaunch

      如果任務的根activity的這個屬性被設置爲"true",在用戶離開任務再回來時,棧中是清空到只剩下根activity.換句話說,它是與alwaysRetainTaskState反着來的.用戶回到任務時永遠見到的是初始狀態,即使只離開了一小會.

  • finishOnTaskLaunch

      這個屬性很像clearTaskOnLaunch,但是它作用於一個單獨的activity,而不是整個任務.它也可以導致任何activity死亡,包含根activity.當它被置爲"true"時,activity只在當前會話中存活.如果用戶離開然後回來,它就已經不在了.

啓動一個task

  你可以設置一個activity爲一個任務的入口,通過給它一個值爲"android.intent.action.MAIN"intent過濾器"和一個值爲"android.intent.category.LAUNCHER"的過濾器.例如:

<activity... >

<intent-filter... >

<actionandroid:name="android.intent.action.MAIN" />

<categoryandroid:name="android.intent.category.LAUNCHER" />

</intent-filter>

...

</activity>



  一個intent這種類型的過濾器導致activity的一個圖標和標籤被顯示於應用啓動界面上.使得用戶可以啓動這個activity並且再次回到這個任務.

  這第二個能力是很重要的:用戶必須能離開一個任務並且之後還能通過啓動器回來.爲此,兩種使得activity永遠在新任務中啓動的啓動模式:"singleTask""singleInstance",應該只在當activity具有ACTION_MAINCATEGORY_LAUNCHER過濾器時使用.想像一下,例如,如果沒有這些過濾器將會發生什麼:一個intent啓動一個"singleTask"activity,在一個新的任務中初始化,並且用戶在這個任務中忙乎了一些時間.然後用戶按下HOME按鈕.任務現在被移到後臺並且不可見了.因爲這個activity不是應用的啓動activity,用戶就再也沒有辦法回到這個任務了.

  但遇到那些你不希望用戶能夠回到一個activity的情況時怎麼辦呢?有辦法:設置<activity>元素的finishOnTaskLaunch屬性爲"true"!

 

轉載請說明出處:http://blog.csdn.net/ff20081528/article/details/17219951




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章