Tasks and Back stack 詳解

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

一個應用往往包含很多 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啟動ActivityB,ActivityA停止,但是系統保存它的狀態(比如滾動條的位置和表單中輸入的文本).如果用戶在Activity B中按下了后退按鈕,ActivityA以保存的狀態恢復.

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

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

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

保存Activity的狀態

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

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

管理tasks

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

可以做這些事情,甚至更多事情,通過設置manifest中<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

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

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

  • 使用manifest文件

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

  • 使用Intent的flag

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

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

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

使用manifest文件

當在你的manifest文件中聲明一個activity時,你可以使用<activity>元素的launchMode屬性指定activity如何與一個任務關聯.
launchMode屬性指明了activity如何啟動到一個任務中去.有四種不同的啟動模式你可以用于指定給launchMode屬性:

  • "standard"(默認模式)

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

  • "singleTop"

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

例如,假設一個任務的后退棧中有根ActivityA和activityB,C,D(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,用戶就不能按下后退鍵回到當前的activity在intent來之前的狀態.

  • "singleTask"

系統創建一個新的任務并且實例化activity為新任務的根.然而,如果一個activity的實例已存在于另一個任務,系統就會通過調用這個activity的onNewIntent()把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的頂端activity.圖4演示了這種情形.

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

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

使用 Intentflags

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

  • FLAG_ACTIVITY_NEW_TASK
      在新的任務中啟動activity-即不在本任務中啟動.如果一個包含這個activity的任務已經在運行,那個任務就被弄到前臺并恢復其UI狀態,然后這個已存在的activity在onNewIntent()中接收新的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>中聲明的默認包名不同,因為系統使用包名來標識默認的任務親和力.
 親和力在以下兩種情況起作用:

  • 當啟動一個activity的intent包含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過濾器).-翻譯得挺難受,這句話也就是說,只要使用了相同的親和力,用戶就能回到這個已啟動的任務中.

  • 當一個activity的allowTaskReparenting屬性為"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_MAIN和CATEGORY_LAUNCHER過濾器時使用.想像一下,例如,如果沒有這些過濾器將會發生什么:一個intent啟動一個"singleTask"activity,在一個新的任務中初始化,并且用戶在這個任務中忙乎了一些時間.然后用戶按下HOME按鈕.任務現在被移到后臺并且不可見了.因為這個activity不是應用的啟動activity,用戶就再也沒有辦法回到這個任務了.
  但遇到那些你不希望用戶能夠回到一個activity的情況時怎么辦呢?有辦法:設置<activity>元素的finishOnTaskLaunch屬性為"true"!

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,251評論 25 708
  • 由于航班的延誤,我不得不改換了行程,比預計的早一天到了敦煌。其實,并未早一天,因為凌晨一點才到青年旅社。旅社老板...
    蝸小五閱讀 348評論 1 2
  • 其實,我打下上面的題目時,我是還不知道要寫些什么的。只知道自己要寫,還要完全一個打賭,不能輸那一塊錢。 如果我有八...
    力牧閱讀 188評論 1 1
  • 有 天 弱 反 生 下 者 者 道...
    虹元閱讀 330評論 0 1