天天看點

Task and Back Stack

Task and Back Stack  如需轉載,請注明轉載位址: http://blog.csdn.net/carrey1989/article/details/9056501      一個Application通常包含多個acitvities。每一個activity應該被設計為實作使用者想做的一些列互相相關的操作,并且可以啟動其他的activities。例如,一個email應用可能會有一個顯示email清單的Activity,當使用者選擇了一封email,一個顯示email内容的新activity就打開了。        一個Activity甚至可以打開同一部裝置中存在于其他Application中的activities。例如,如果你的應用想要發送一封email,你可以定義一個intent來執行"send" action,并添加一些data,比如email address和message,然後調用startActivity()。另一個應用中如果有一個Activity聲明了它可以處理這種類型的intent,那麼這個activity就會打開。在這種情況下,這個intent想要發送email,于是一個email application内部的activity啟動(如果有多個activity支援這種相同的intent,那麼系統會讓使用者來選擇使用哪一個)。當郵件被發送出去了,你的activity繼續工作就好像之前那個email activity是你的application的一部分一樣,盡管這些activity可能來自于不同的application。Android提供了這種無縫的使用者體驗,可以讓這些activity保持在同一個task中。

     一個task是一系列activity的集合,使用者可以與這一系列的activity進行互動來完成某一項工作。這些activity排列在一個棧中(the "back stack"),按照它們被打開的順序進行排列。

     裝置的Home Screen是大多數task的起點。當使用者在launcher中觸摸application的圖示的時候(或者在Home screen中的快捷方式),那個application的task就會來到foreground,如果目前不存在這個application的task(這個application最近沒有被用過),那麼一個新的task就會被建立,并且那個application的 "main" activity就會被建立打開作為 stack 中的 root activity。

     如果目前的activity啟動了另一個activity,新的activity就會被push到stack的頂部并且獲得焦點,之前的activity繼續儲存在stack中,但是處于stopped狀态。當一個activity stop的時候,系統會儲存其目前的user interface狀态,當使用者按下Back按鈕,目前的Activity會被popped from棧的頂部(這個activity會被destroyed),并且之前的activity會resumes(之前儲存的UI狀态會被恢複)。stack中的activities永遠不會重新排列,隻會被從stack中pushed和popped—— 當一個activity被從目前的activity啟動的時候會被pushed onto the stack,當使用者點選Back按鍵的時候被popped off。back stack是一個類似 "last in,first out"的結構。圖檔1展示了在目前的back stack中,activities在不同的時間點互相之間的變化情況。

Task and Back Stack

圖1.顯示了一個建立的activity如何添加到back stack。當使用者點選Back按鈕,目前的activity被destroyed,前一個activity resumes。

     如果使用者繼續點選Back,那麼stack中的每一個activity都被popped off,之前的activity顯示,直到使用者回到了Home screen(或者啟動task的那個activity)。當所有的activities被從stack中移除之後,task就不複存在。

     一個task是一個可以移動到background的整體單元,當使用者開始一個新的task或者通過Home鍵前往Home screen的時候,目前的task會被移動到background。當處于background的時候,task中所有的activities都處于stopped狀态,但是task的back stack保持完整——在另一個task獲得焦點的時候這個task失去焦點,就像圖檔2中顯示的一樣。

Task and Back Stack

圖2.兩個task:Task B位于foreground接受使用者的互動,Task A 位于 background,等待重新被resumed。

     一個background task可以回到foreground,這樣使用者就可以從離開時的狀态繼續進行操作。例如,目前的task(Task A)的stack中有三個activities——目前的activity下面還有兩個activities。使用者點選Home鍵,然後從application launcher啟動一個新的application。當Home screen出現的時候,Task A進入background。當新的application啟動,系統為那個application啟動一個新的task(Task B),這個task有自己的stack of activities。當使用者與新開啟的application互動完畢之後,回到了Home screen 然後選擇最初啟動Task A的那個應用。現在Task A來到了foreground——它的stack中的所有三個activities都是完整的并且處于stack頂部的那個activity resumes(進入resumed狀态)。這個時候,使用者也可以通過回到Home screen并選擇啟動Task B的那個application來切換回Task B(或者通過長按Home 按鈕來檢視最近的 tasks 然後選擇一個)。這是Android上多任務的一個例子。

注意:多個tasks可以同時被保持在background。但是,如果使用者同時運作了很多background tasks,系統可能會在記憶體不足的時候destroying background activities 來回收記憶體,這會造成activity states的丢失。具體請閱讀下面的章節。

     因為back stack中的activities永遠都不會重新排列,如果你的應用允許使用者從不止一個activity中啟動一個特殊的activity,那麼一個新的特殊的activity的執行個體會被建立并被pushed onto the stack(而不是将之前的執行個體移動到棧頂)。也就是說,你應用中的一個activity可能會被執行個體化很多次(甚至是從不同的tasks裡),如圖3中所示。

Task and Back Stack

圖3.一個activity被執行個體化多次。

     這樣一來,當使用者通過使用Back button來傳回之前的頁面的時候,每一個這個特殊activity的執行個體都會按照他們被打開的順序重新呈現給使用者(每一個都有自己的UI state)。但是,如果你不想要讓一個activity執行個體化多次,你可以更改這種行為。更改的方法在下面的文章中有所講解。

     總結一下activities 和 tasks的預設行為:

  • 當Activity A 啟動了 Activity B,Activity A就處于 stopped狀态,但是系統會保持它的狀态(比如scroll position 和 text entered into forms)。如果使用者在Activity B顯示的時候點選Back按鈕,Activity A會以其之前保持的狀态resumes。
  • 如果使用者通過點選Home button離開了一個task,目前的activity就會stopped,并且它的task會進入background。系統會保持task中的每一個activity的狀态。如果使用者通過選擇開始task的launcher icon來resumes the task,the task會回到foreground并且resumes the activity at the top of the stack(棧頂的activity顯示并回到resumed狀态)。
  • 如果使用者點選了Back button,目前的activity會popped from the stack and destroyed(彈棧并銷毀)。在stack中的之前的activity會回到resumed狀态。當一個activity is destroyed,系統就不會保持這個activity的state了。
  • Activities可以被執行個體化很多次,及時是從不同的tasks中。

Navigation設計      要學習更多關于Android應用中navigation的工作和設計方式,請閱讀Android Design's Navigation guide。

儲存Activity狀态      正如上面所讨論的,系統預設會在activity stop的時候儲存它的狀态。這樣一來,當使用者傳回之前的activity的時候,它的使用者界面會顯示為使用者離開它時候的樣子。然而,你也可以并且應該主動地通過callback方法來儲存activities的狀态,以防activity被destroyed而必須recreated。

     當系統stops one of your activities(比如當一個新的activity啟動或者task移動到background),系統在記憶體不足的時候可能會destroy這個activity。當這種情況發生時,activity的state資訊就丢失了。如果這種情況發生了,系統還是會知道這個被destroy的activity在back stack中有一個位置,但是在activity被帶回the top of the stack(棧頂)的時候,系統必須要recreate it(而不是resume it)。為了避免丢失使用者的操作資訊,你應該通過實作onSaveInstanceState() callback方法來在你的activity中主動儲存狀态。

     要學習更多關于儲存activity state的内容,請閱讀Activities文檔。

管理Tasks      正如上面所提到的,Android管理tasks和back stack的方式是——按照同一個task中的成功啟動的activities的順序将其填入一個"後進先出"的stack——在大多數的應用中都足以滿足需要,你并不需要去關心你的activities與tasks之間的關系或者它們在back stack中的排列方式。然而,有的時候,你可能會想要改變這種預設的規則。你可能會想讓你應用中的某一個activity在一個新的task裡啟動(而不是在目前的task裡啟動);或者,當你啟動一個activity,你想要使用一個已經存在的該activity的執行個體,并将其提升到棧頂(之前的activities将popped off),而不是在back stack的頂部建立一個新的activity執行個體;再或者,你希望在使用者離開目前的task的時候,back stack會把除了root activity之外的所有activities都clear。

     使用manifest檔案中的<activity>節點中的屬性以及通過向startActivity()方法的Intent參數設定flags,你可以實作以上的功能,還可以實作更多其他功能。

     與管理task相關的主要的<activity>節點屬性如下:

  •      taskAffinity
  •      launchMode
  •      allowTaskReparenting
  •      clearTaskOnLaunch
  •      alwaysRetainTaskState
  •      finishOnTaskLaunch

     與管理task相關的主要的intent flags如下:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP

     在下面的章節中,你将會看到如何使用這些manifest屬性以及intent flags來定義activities與tasks之間的關系以及如何定義activity在back stack中的行為。

注意:大多數應用都不應該改變預設的activities和tasks行為。如果你确實認為有必要更改這種預設的行為,一定要確定測試activity在lanuch的時候以及從其他activities和tasks傳回的時候可用(有執行個體)。確定測試那些可能與使用者的期望相沖突的activities導航操作。

定義launch modes

     launch modes允許你定義一個建立的activity執行個體與current task的關系。你可以通過兩種不同的方式來定義不同的launch modes:

  • 使用manifest檔案

     當你在你的manifest檔案中聲明activity的時候,你可以指定當activity啟動的時候應該如何與tasks關聯。

  • 使用Intent flags

     當你調用startActivity()的時候,你可以為Intent對象設定一個flag,這個flag聲明了這個新的activity執行個體應該如何(或者是否)與current stack相關聯。

     如此一來,如果Activity A啟動Activity B,Activity B可以在它的manifest中定義它應該如何與current task(如果會關聯的話)相關聯,Activity A也可以主動來請求定義Activity B與current task之間的關聯方式。如果這兩個activities都定義了Activity B與tasks的關聯方式,那麼Activity A的請求(定義在intent中)會 優先于Activity B的請求(定義在manifest中)。

注意:有一些launch modes在manifest檔案中能夠定義但是在intent對象的flags中沒有對應的定義方式。同樣的,有一些launch mode在intent對象的flags中可以定義但是在manifest檔案裡沒有對應的定義方式。

使用manifest檔案

     當在manifest檔案中聲明一個activity的時候,你可以通過使用<activity>标簽的launchMode屬性來設定這個activity應該如何與tasks關聯。

     launchMode屬性指定了activity如何launch into a task(在一個task中啟動)。你可以指定四種不同的launch modes 給launchMode屬性:

      "standard"(預設mode)           預設模式。系統會在啟動這個activity的task中建立這個activity的一個執行個體并将intent傳遞給該執行個體。這個activity可以被執行個體化很多次,每一個執行個體可以屬于不同的tasks,一個task也可以有多個執行個體。

      "singleTop"           如果目前的task已經有了一個activity的執行個體并且處于目前task頂部,系統會通過調用該執行個體的onNewIntent()方法來把intent傳遞給該執行個體,而不是建立這個activity的一個新的執行個體。這個activity依然可以被執行個體化很多次,這些執行個體可以屬于不同的tasks,一個task也可以擁有多個執行個體( 但是隻有在back stack頂部的activity并不是要啟動的這個activity的執行個體的情況下)。

          例如,假設一個task的back stack是這樣排列的:一個root activity A 上面是 activities B,C 頂部是D(這個stack是 A-B-C-D;D位于頂部)。這時收到一個要啟動activity D的intent。如果D的launchMode是預設的"standard",那麼一個新的activity D的執行個體會被建立,并被launched,stack變成A-B-C-D-D。然而,如果D的launchMode是"singleTop",那麼頂部已存在的D的執行個體會通過onNewIntent()方法收到intent,stack保持A-B-C-D。但是,如果收到一個啟動activity B的intent,那麼一個新的B的執行個體會被添加到stack中,就算B的launchMode是"singleTop"。

          注意:當一個 Activity的新的執行個體被建立,使用者可以通過按Back button來傳回到之前的activity。但是當一個已經存在的activity執行個體處理了a new intent(onNewIntent()),使用者不可能通過按Back button來傳回到這個activity在onNewIntent()回調處理new intent之前的狀态。

     "singleTask"           系統會建立一個新的task并執行個體化activity作為這個新的task的root activity。但是,如果這個activity的一個執行個體已經在一個task中存在了,那麼系統會把intent傳遞給那個執行個體并調用其onNewIntent()方法。而不是建立一個新的執行個體,在同一時間隻能有一個該activity的執行個體存在。

          注意:盡管這個activity在一個新的task中啟動,Back button還是會把使用者帶回到之前啟動它的activity,而不是Home screen。

     ”singleInstance“           跟"singleTask"很類似,不同的是,系統不會在保持”singleInstance“類型activity的task中再launch任何的activities。也就是說這個"singleInstance" activity永遠是它所在的task内唯一的activity;這個activity啟動并打開的所有的activities都會在另一個task中啟動。

          舉個例子,Android Browser application聲明了它的web browser activity應該永遠在它自己的task中運作——通過在<activity>标簽中指定singleTask launch mode來實作。這意味着如果你在自己的application中釋出一個打開Android Browser的intent,那麼啟動的 activity不會進入你自己的application的task中。可能的情況是,要麼一個新的task為Browser開啟,要麼如果Browser已經有一個task在background running,那個背景的task會被帶到前端來處理the new intent(onNewIntent())。

          不管一個activity是在一個新的task中啟動還是與啟動它的activity在同一個task。按Back button總會把使用者帶回到之前的activity。然而,如果你啟動的activity指定了singleTask launch mode,而且在background task中已經存在一個這個activity的執行個體,那麼整個background task都會被帶到foreground。這樣的話,back stack中将包含所有brought forward的那個task中的activities,位于stack的頂部,圖4說明了這種場景。

Task and Back Stack

圖4.launch mode定義為"singleTask"的activity是如何被添加進back stack的。如果這個activity已經有一個執行個體,并且這個執行個體是一個擁有自己的back stack的background task的一部分,那麼整個的back stack都會comes forward,位于current task的頂部。

          關于更多的關于manifest檔案中的launch mode的内容可以檢視<activity>節點的文檔,關于launchMode屬性和其屬性值有更詳細的讨論。           注意:通過launchMode屬性為activity指定的行為可以被啟動該activity的intent中設定的flags所 影響,下面将對這種情況進行讨論。

使用Intent flags

     當要啟動一個activity的時候,你可以通過給傳遞給startActivity()方法的intent添加flags來修改預設的activity與task之間的關聯。可以用來修改預設行為的flags如下:

      FLAG_ACTIVITY_NEW_TASK

          在新的task裡啟動the activity。如果已經有一個背景運作的task裡面有要啟動的activity的執行個體,那麼這個task将來到foreground并恢複之前儲存的state,其中的activity将會在onNewIntent()回調中收到intent。

          這種設定方式與前面在launchMode中設定"singleTask"将産生相同的效果。

      FLAG_ACTIVITY_SINGLE_TOP

          如果要啟動的activity是current activity(在back stack的頂部),那麼頂部的執行個體将在onNewIntent()中收到intent,而不是建立一個新的activity執行個體。

          這種設定方式與之前在launchMode中設定"singleTop"将産生相同的效果。

     FLAG_ACTIVITY_CLEAR_TOP

          如果在current task中已經有一個要啟動的activity的執行個體,那麼将 不會launch一個新的activity的執行個體,所有在已有的該activity的執行個體之上的activities都會被destroyed,the activity執行個體會回到resumed狀态并位于back stack頂部,intent會被傳遞給該activity的執行個體的onNewIntent()方法。

          在launchMode屬性中,沒有屬性值可以定義這種行為。

          FLAG_ACTIVITY_CLEAR_TOP經常與FLAG_ACTIVITY_NEW_TASK結合在一起使用。當這二者在一起用的時候,可以在其它的task中找到存在的activity執行個體并使其位于一個可以響應到intent的位置(彈空其上的activities)。

          注意:有一種情況的處理稍有特殊, 如果指定的activity的launchMode是"standard",并且在task中已經有一個目标activity的執行個體,那麼這個執行個體(以及之上的activities)也會被從stack中移除,然後一個新的執行個體會在原來的執行個體的位置被launched并處理incoming intent。 這是因為在launchMode是"standard"的時候一定會建立一個新的activity執行個體來接受傳遞的intent并處理 。 (這說明Intent flags 和 launchMode 之間是結合起來影響task的)

處理affinities

     affinities表示一個activity更 傾向于從屬于哪一個task。預設情況下,相同application内的所有的activities彼此之間情投意合(互相相關)。是以,預設情況下,一個app内的所有的activities都傾向于相同的task。然而,你可以修改activity的 default affinity。不同app裡的activities可以共享相同的affinity,或者同一個app内的不同的activities可以被指定為不同的task affinities。

     你可以通過修改<activity>節點的taskAffinity屬性來修改指定activity的affinity。

     taskAffinity屬性設定一個string類型的值 (注意這個String的格式是有要求的,必須是x.x,比如a.b,而且每一個"."分割的部分必須用字母開頭不能用數字,簡單說,就是一個包結構類型的String),這個值必須不同于在<manifest>節點中定義的package name,因為系統預設會使用這個name作為本應用預設的task affinity。

     affinity在兩種情況下會起作用:

  • 當launch一個activity執行個體的intent包含FLAG_ACTIVITY_NEW_TASK flag的時候。預設情況下,一個新的activity執行個體會被launched into到調用startActivity()的那個activity所在的task。它會被pushed onto調用者所在的back stack。然而,如果傳遞給startActivity()的intent包含FLAG_ACTIVITY_NEW_TASK flag,系統會尋找一個不同的task來容納新的activity執行個體。通常,這是一個new task。但是,這也不一定。如果已經有了一個task并且這個task的與新的activity執行個體有相同的affinity,那麼這個新的activity執行個體會被launched into that task。如果不是這種情況,那就啟動一個新的task。如果設定了這個flag進而導緻一個activity執行個體在一個新的task裡啟動然後使用者按了Home button離開它傳回了主界面,此時應該有一種途徑可以讓使用者傳回之前的task。有一些對象(比如notification manager)總是在另外的task中來啟動activities,而不是作為定義Intent的activity所在的task的一部分,是以它們在調用startActivity()方法的時候,會給參數intent設定FLAG_ACTIVITY_NEW_TASK。如果你有一個會被一個外部對象(比如上面說的notification manager)調用的activity,并且intent中會設定FLAG_ACTIVITY_NEW_TASK,應該確定使用者有一種途徑可以傳回activity所在的這個task,比如通過launcher上的一個icon(task的root activity應該有CATEGORY_LAUNCHER intent filter;參見下面的讨論,有詳細的解釋)。
  • 當一個activity有allowTaskReparenting屬性并且設定為"true"的時候。在這種情況下,這個activity可以從啟動它的task移動到affinity相同的task(the task it has an affinity for)——當這個有相同affinity的task來到foreground的時候。例如,假設有一個預報標明城市的天氣情況的activity,這個activity被定義為一個旅遊應用的一部分。它與它所在應用中的其他的activities有相同的affinity(預設的application affinity)并且它通過設定allowTaskReparenting屬性為true允許re-parenting。當你的應用中的activity啟動了這個天氣預報的activity,它最開始的時候會屬于你的應用中啟動它的activity所在的task。然而,當旅遊應用的task來到foreground,這個天氣預報的activity會被重新指定到旅遊應用的task(會直接添加到棧頂并顯示,但是測試後隻有在點選launcher中的圖示來使task來到foreground才能得到這種效果,通過Recents來打開最近任務卻不行)。

提示:如果想要實作從使用者的角度看來一個.apk檔案包含了多個"application",可以使用taskAffinity屬性來為不同的activities指定不同的attinities,其中affinity相同的activities屬于同一"application"。

清空back stack

     如果使用者離開一個task很長很長時間沒有傳回(通常30min以上),系統将會清空task中除了root activity之外的所有的activities,當使用者傳回到task的時候,隻有root activity被恢複。系統會這樣做是因為,經過一個比較長的時間之後,使用者可能已經忘記了或者不再關心,放棄了他們之前做的操作,就算之後再次傳回task也是為了做一些重新開始的操作。

     如果想要修改這種預設的行為,可以通過下面的<activity>屬性:

      alwaysRetainTaskState

          如果在task的root activity中,這個屬性被設定為“true”,那麼上面描述的預設行為将不會發生。即使過了很長時間(30min)task還是會在stack中保持所有的activities。

      clearTaskOnLaunch

          如果在task的root activity中,這個屬性被設定為"true",那麼無論何時,主要使用者離開了目前的task并傳回的時候,stack都已經被清空 包括root activity。也就是說,這個屬性剛好與alwaysRetainTaskState相反。使用者在傳回task的時候看到的永遠都是它的初始狀态,即使是使用者隻離開了一小段時間。

     finishOnTaskLaunch

          這個屬性與clearTaskOnLaunch類似,不同的是它影響的是特定的一個activity,而不像clearTaskOnLaunch影響的的是 整個的task。這個屬性可以使得任何一個activity消失,包括root activity。當這個屬性的值被設定為"true"的時候,隻有當activity所在的task依然屬于使用者的目前會話的時候(current session,簡單說就是使用者看得見的時候,foreground),這個activity才會被保持。如果使用者離開這個task然後傳回,這個activity将不再存在,不會出現了。

啟動一個task

     你可以通過intent filter來設定一個activity作為一個task的入口(entry point)。具體設定辦法是:在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>設定的icon和label被顯示到application launcher(安裝即顯示),為使用者提供了啟動該activity以及傳回之前建立并啟動過的task的途徑(這個task的root activity應該是這裡的設定了intent filter的activity)。

     這樣的設定方式還有一個很重要的用途:使用者可以通過這個activity在launcher中的圖示,在離開相應的task之後通過點選圖示傳回該task。是以,launchMode中的那兩個表示activities啟動的時候可能會開始一個新的task的mode值:”singleTask“和"singleInstance", 應該隻用在當activity有ACTION_MAIN和CATEGORY_LAUNCHER filter設定的情況下。想象一下,如果沒有這個filter設定:一個intent啟動了一個"singleTask"的activity,開啟了一個新的task,并且使用者在這個task中做了不少的操作。如果使用者按了Home button。這個task進入了background不可見了。此時使用者就沒有辦法來傳回這個task了,因為在application launcher中找不到代表該task或者說其root activity的圖示(入口)。

     如果你不希望使用者傳回已經進入background的activity,設定<activity>的finishOnTaskLaunch屬性為"true"(見上面關于”清空back stack“的讨論)。