活動 —— 任務與後退堆棧

文章譯自:http://developer.android.com/intl/zh-CN/guide/components/tasks-and-back-stack.html



內容內容


  1. 保存活動狀態
  2. 管理任務
    1. 定義啓動模式
    2. 處理親緣關係
    3. 清理後退堆棧
    4. 啓動一個任務

 



快速瀏覽


  • 所有活動都屬於任務。
  • 任務內包含順序排列的活動集合,用戶以此順序與它們交互。
  • 任務可以移動到後臺並保留每個活動的狀態來讓用戶去執行其他的任務而不丟失他們之前的操作


一個應用通常包括多個活動。應該圍繞着一個特定的用戶可以執行並開始其他活動的行爲類型來設計每個活動。例如,郵件應用可能有一個活動來顯示新郵件的列表。當用戶選擇一個郵件時,一個新的活動被打開以查看該郵件。


一個活動甚至可以啓動存在於設備上的其他應用裏的活動。例如,如果你的應用想發送一封郵件,你可以定義一個意圖來執行“發送”動作,幷包含一些數據,譬如郵件地址和消息。接着,一個來自其他應用裏宣稱可以處理此類意圖的活動被打開。在這種情況下,意圖是爲了發送郵件,所以郵件應用的“編輯”活動開始啓動(如果多個活動都支持相同的意圖,那麼系統會讓用戶選擇一個來使用)。當郵件被髮送時,恢復你的活動並且看上去好像郵件活動是你應用的一部分。儘管活動可能來自不同的應用,但Android可以通過把兩個活動保持在相同的任務中來維持無縫的用戶體驗。


任務就是一個當執行某項工作時用戶與之交互的活動集合。活動以它們被打開的順序排列在堆棧中(即“後退棧”)。


設備的主屏幕是絕大多數任務開始的地方。當用戶在應用程序啓動器中觸擊一個圖標時(或主屏幕上的快捷方式),那個應用的任務變來到前臺(即,該任務之前已被創建,並且位於後臺)。如果尚未存在該應用的任務(應用最近沒有被使用),那麼一個新的任務被創建,然後應用的“主”活動作爲堆棧內的根活動被打開。


噹噹前活動開始其他的活動時,新的活動被推至堆棧頂部,並獲得焦點。先前的活動仍然在堆棧裏,但是被停止了。當一個活動被停止時,系統保留活動用戶界面的當前狀態。當用戶按下Back按鈕時,當前的活動從棧頂彈出(該活動被銷燬了),接着,先前的活動被恢復(活動UI先前狀態被恢復)。堆棧內的活動從未從新排列,只是推向和彈出堆棧—— 當由當前活動開始新活動時,新活動被推到棧頂,當用戶使用Back按鈕離開它時,它從棧頂彈出。因此,後退堆棧運作爲“後進,先出”的對象結構。圖1以一條時間線可視化了此行爲,該時間線顯示了活動和當前堆棧間在每個時間點上的進展。

   

圖-1說明了任務中的每個新的活動如何被添加到後退堆棧中。當用按下Back按鈕,當前的活動被銷燬,然後先前的活動被恢復。


如果用戶繼續按Back按鈕,那麼堆棧內的每個活動都彈出以揭露先前的活動,直到返回到主屏幕(或開始任務時任何正在運行的活動)。當所有的活動都被移出堆棧,任務便不再存在了。


任務是具有凝聚性的單元,當用戶開始一個新的任務或通過Home按鈕轉到主屏幕時它可以移向“後臺”。在後臺時,所有任務內的活動都被停止了,但是任務的後退堆棧依然完整 — 它只是在其他任務發生時簡單地失去了焦點,如圖-2所示。然後,任務可以回到“前臺”,這樣用戶就可以從他們離開的地方重新開始。例如,假定當前任務(Task A)擁有三個活動在它的堆棧裏 — 兩個位於當前活動之下。用戶按下Home按鈕,然後從應用管理中開始了一個新的應用。當主屏幕出現時,Task A進入後臺。當新的應用開始時,系統爲這個應用開始一個新的任務(Task B),並擁有它自己的活動堆棧。同該應用交互後,用戶又返回到主屏幕並選擇最初開始Task A的應用。此時,Task A來到前臺 — 其堆棧內所有三個活動都是完整的,並且堆棧頂部的活動得以恢復。此刻,用戶仍可以通過回到主屏幕並選擇開始Task B的應用圖標來切回到Task B(或者通過觸碰並按住Home按鈕來顯示最近的任務,然後選擇一個)。這是一個Android上多任務的例子。


    

圖 - 2. 兩個任務: 當Task A 在後臺等待被恢復時,Taks B在前臺接收用戶的交互。


注意: 後臺可以同時持有多個任務。然而,如果用戶同時在後臺運行個任務,系統可能開始銷燬一些後臺活動以恢復內寸,這會導致丟失這些活動的狀態。請參閱接下來有關活動狀態的章節



因爲活動在後退堆棧裏從未重新排列,所以,如果應用允許用戶從不只一個活動裏開始某個特定活動,則那個活動的新實例被創建並推入堆棧裏(而不是把該活動先前的實例提至堆棧頂部),如圖 - 3所示。


       


圖 - 3. 單個活動被創建多次。


同樣,如果用戶使用Back按鈕向後瀏覽,活動的每個實例以它們的創建順序被顯示出來(伴隨着它們自己的UI狀態)。然而,你也可以改變這個行爲,如果不想讓活動被創建多次的話。在稍後的關於管理任務章節裏討論如何這樣做。


總結活動和任務的默認行爲:

  • 當活動A開始活動B,活動A被停止,但是系統會保留它的狀態(諸如滾動位置和輸入窗體的文本)。如果用戶在活動B中按下Back按鈕,活動A隨同它被保存的狀態得以恢復。
  • 如果用戶通過按Home按鈕離開一個任務,則當前的活動被停止,同時它的任務進入後臺。系統保留任務裏每個活動的狀態。如果用戶稍後通過選擇開始該任務的啓動圖標來恢復該任務,則該任務重新來到前臺並在堆棧頂部恢復活動。
  • 如果用戶按下Back按鈕,當前活動從堆棧內彈出並銷燬。此時,堆棧內上一個活動被恢復。當活動被銷燬時,系統不會保留該活動的狀態。
  • 活動可以被創建多次,甚至從其他的任務裏。


導航設計

爲了解更多關於在Android上應用導航是如何工作的,請閱讀Android設計之導航指南。


1. 保存活動狀態



如上所述,當活動被停止時,系統的默認行爲是保存它的狀態。這樣一來,當用戶向後瀏覽至先前的活動時,該活動的用戶界面呈現爲他們離開它時的樣子。然而,在活動被銷燬以及被重建的情況下,你可以 — 及應該 — 通過使用回調方法來主動地保留你的活動狀態。


當系統停止其中的一個活動時(譬如當新活動開始或任務移至後臺時), 系統可能會立刻銷燬那個活動,如果它需要恢復系統內存的話。當這種情況發生時,關於該活動的狀態信息就丟失了。如果發生這種情況,系統依然知道那個活動在後退堆棧中的位置,但是當活動被提到堆棧頂部時,系統必須再次創建它(而不是恢復它)。爲了避免丟失用戶的操作,你應該主動地保存這些操作,通過在你的活動裏實現onSaveInstanceState()回調方法。


更多關於如何保存活動狀態的信息,請參閱活動文檔。




2. 管理任務



如上所述 , 通過把所有已啓動的活動連續放置在相同的任務裏及"後進,先出”的堆棧中 ,Android這樣管理任務和後退堆棧的方式對大多數應用來說都可以很的好運作,並且不必擔心活動和任務是如何關聯的或它們在堆棧中是如何存在的。


然而,你可能決定打算中斷這種常規行爲。你可能想讓一個自己應用內的活動在其被啓動時來開始一個新的任務(而不是被放置在當前任務裏);或者,當你開始一個活動時,想要把該活動現有的實例提前(而不是在後退堆棧頂部創造一個新的實例);或者,當用戶離開任務時,你要讓後退堆棧清除所有除根活動以外的所有活動。


通過清單文件內的<activity>元素以及傳遞給startActivity()的意圖內的標誌,你可以實現上述想法,或者更多。


就這一點而言, 可以使用的最主要的<activity>屬性有:


以及可供使用的主要意圖標誌有:


在接下來的章節中,你將看到如何使用這些屬性和意圖標誌來定義活動與任務的關聯以及在它們後退堆棧內的行爲。


注意: 大多數應用不應該中斷活動和任務的這種默認行爲。如果你決定對你的活動來說需要改變這種默認行爲,請謹慎使用並一定要測試活動在啓動期間以及通過Back按鈕從其他活動和任務向後導航至它時的可用性。一定要測試可能與用戶期望行爲相沖突的導航行爲。


2.1 定義啓動模式


啓動模式允許你定義活動實例和當前任務間的關聯。可以通過兩種方式來定義不同的啓動模式:

  • 使用清單文件

    當在清單文件中聲明活動時,可以指定活動在其啓動時與任務的關聯。

  • 使用意圖標誌

    當調用startActivity()時,可以在意圖中包含一個標誌,它說明了新活動應該如何(或是否)與當前任務關聯。


因此,如果活動A開始了活動B,活動B可以在其清單文件定義它應該如何與當前任務關聯(如果有的話),同時活動A也可以要求活動B應該如何與當前任務關聯。如果兩個活動都定義了活動B與當前任務的關聯,那麼活動A的請求(依照意圖中的定義)將優先於活動B的請求(依照清單文件中的定義)。


注意:  一些對於清單文件可用的啓動模式不可以作爲意圖標誌,同樣,一些可以作爲意圖標誌的啓動模式不可以在清單文件中定義。


2.1.1  使用清單文件


當在清單文件中聲明活動時,你可以通過使用<activity>元素的launchMode屬性來指定活動應該如何與任務進行關聯。

launchMode 屬性指定了一條活動應該如何被啓動到任務裏的指令。有四個不同的啓動模式,你可以將它們賦值給launchMode屬性:


"standard" (默認模式)
默認情況下,系統從活動開始的任務裏創建活動的新實例,並將意圖發送給它。該活動可以被多次創建,每個實例可以屬於不同任務,同時,一個任務可以擁有該活動的多個實例。

"singleTop"
如果活動的一個實例已經存在於當前任務的頂部,則系統通過調用該活動的onNewIntent()方法把意圖發送給那個實例,而不是創建一個活動實例。該活動可以被創建多次,每個實例可以屬於不同的任務,並且一個任務可以擁有該活動的多個實例(但是僅當後退堆棧頂部的活動不是該活動的一個現有實例)。

例如,假定一個任務的後退堆棧由根活動A隨同活動B,C和頂部的D構成(堆棧爲A-B-C-D;D在頂部)。 當被髮送的意圖是爲了啓動活動D時,如果D的啓動模式爲默認的"standard",那麼該類一個新實例被創建,同時堆棧的變成A-B-C-D-D。 然後,如果D的啓動模式爲"singleTop",  則D的現有實例通過onNewIntent()接受該意圖,因爲它已在堆棧頂部,所以,堆棧依舊是A-B-C-D。然而,如果發送的意圖使爲了啓動活動B,則一個新的活動B的實例被添加至堆棧頂部,即使它的啓動模式爲"singleTop"


注意:當活動的一個新的實例被創建時,用戶可以按Back按鈕返回先前的活動。但是,當活動的一個現有實例處理了新的意圖,用戶就不能通過按Back按鈕返回到新意圖抵達onNewIntent()方法之前時的該活動的狀態。


"singleTask"

系統創建一個新的任務並在新任務的底部初始化活動。然而,如果活動的一個實例已經存在於某個單獨的任務裏,則系統會通過調用該活動的onNewIntent() 方法把意圖分派給其現有的實例,而非創建一個新的實例。同一時刻只能存在一個該活動的實例。


注意: 儘管活動在新的任務中開始,但Back按鈕依然可以使用戶返回到先前的活動。


"singleInstance".

"singleTask"一樣, 除了系統不會向持有實例的任務裏啓動任何其他活動。該活動通常是它的任務的單個且唯一的成員;任何通過此模式開始的活動都是在一個單獨的任務中打開。


再如,Android瀏覽器應用聲明web瀏覽器活動通常應該在其自己的任務中打開,即在<activity>元素中指定“singTask”啓動模式。這意味着,如果你的應用程序發送一個意圖以打開Android瀏覽器,則它的活動不會被放置在與你的應用相同的任務中。相反,要麼爲瀏覽器開始一個新的任務, 或者,如果瀏覽器已經擁有一個任務運行在後臺,則該任務被提前以處理新的意圖。


無論活動是否在一個新的任務中或者與啓動它的活動的任務相同的任務中開始,Back按鈕通常可以把用戶帶至先前的活動。然而,如果你啓動的活動指定了singTask啓動模式,那麼如果該活動已存在於後臺任務中,則該任務被提至到前臺。此時,後退堆棧在其堆棧頂部包含了所有來自被提前的任務裏的活動。圖-4說明了此類情況



圖-4. 闡述了帶有“singTask”啓動模式的活動是如何被添加到後退堆棧裏的。如果活動已經是一個擁有自己後退堆棧的後臺任務的一部分,那麼整個該後退堆棧也要提前,並置於當前任務的頂部。


更多關於在清單文件內使用啓動模式的內容,請參閱<activity>元素文檔,在那裏,有launchMode屬性和可接受值的更多討論。


注意: 開始活動的意圖內包含的標記可以覆蓋掉通過launchMode屬性爲活動指定的行爲,如下一個章節中的討論。


2.1.2  使用意圖標誌


當開始一個活動時,你可以通過在遞送給startActivity()方法的意圖裏包含標誌來修改活動和它的任務間的關係。可以用來修改默認行爲的標誌有:

FLAG_ACTIVITY_NEW_TASK在新的任務裏開始活動。如果正在開始的活動的任務已經爲其運行,則該任務隨其最後被保留的狀態提前到前臺,然後活動用onNewIntent()方法接收新的意圖。

這產生了和“singleTasklaunchMode屬性值相同的行爲,如上一章節的討論。


FLAG_ACTIVITY_SINGLE_TOP如果正被開始的活動就是當前的活動(在堆棧頂部),那麼現有的實例收到onNewIntent()的調用,而不是創建一個該活動的新實例。

這產生了和 "singleTop" launchMode屬性值一樣的行爲,如上一章節的討論。


FLAG_ACTIVITY_CLEAR_TOP如果正被開始的活動已經運行在當前的任務中,那麼所有處在該活動之上的活動都被銷燬,並且意圖通過onNewIntent()被遞送給該活動剛被恢復的實例(現在位於頂部),而不是啓動一個此活動的新的實例。沒有launchMode屬性值可以產生該行爲。

FLAG_ACTIVITY_CLEAR_TOP最常與FLAG_ACTIVITY_NEW_TASK共同使用。當一起使用時,這些標誌就是一個在其他任務裏定位現有活動並把它放置在一個可以  響應意圖的位置的方法。


注意: 如果目標活動的啓動模式爲"standard",它也要被移出堆棧,同時一個新的實例在其位置上被啓動以處理接收的意圖。這是因爲當活動的啓動模式爲"standard"時,通常要爲新的意圖創建該活動的新實例。——沒搞懂這句是什麼意思!大哭


2.2   處理親緣關係


親緣關係說明活動傾向於屬於哪個任務。默認情況下,所有來自同一個應用的活動彼此間具有親緣關係。所以,默認情況下,同一個應用內所有活動更喜歡在同一個任務裏。然而,你可以改變這種默認的活動親緣關係。定義在不同應用內的活動可以共享親緣關係,或者,定義在同一個應用內的活動可以被指定不同的任務親緣關係。


可以通過<activity>元素的taskAffinity屬性來修改任何一個指定活動的親緣關係。


taskAffinity屬性需要一個字符串值,它必須獨一於默認的聲明在<manifest>元素內的包名,因爲系統使用這個名字(指包名)來識別應用程序的默認任務親緣關係。


親緣關係在兩種情況下發揮作用:

默認情況下,新活動被載入了調用startActivity()的活動的任務裏。它被推入與調用者相同的後退堆棧。然而,如果傳遞給startActivity()的意圖內包含FLAG_ACTIVITY_NEW_TASK標誌,系統將尋找一個不同的任務來容納這個新的活動。通常,它是一個新的任務。然而,這不是必須的。如果已經存有一個與該新活動有相同親緣關係的任務,那麼該活動被載入此任務。如果不存在這樣的任務,該活動開始新的活動。

如果該標誌導致活動開始新的任務,然後用戶按Home按鈕離開了它,因此必須有方法讓用戶導航回該任務。一些實體(譬如通知管理器)通常在外部任務裏啓動活動,它從未將這些活動作爲其自身的一部分,所以,它們常常在傳遞給startActivity()的意圖內放置FLAG_ACTIVITY_NEW_TASK標誌。如果你有一個可以由使用了該標誌的外部實體啓動的活動,注意,用戶需要有獨立的方法返回由該活動啓動的任務,比如通過啓動圖標(任務的根活動擁有CATEGORY_LAUNCHER意圖過濾器;請參與下面的啓動一個任務章節)。

這種情況下,活動可以從它開始的任務中移至到與它有親緣關係的任務,當該任務來到前臺時。

比如,假定有一個作爲旅行應用程序一部分的活動,它可以報告選定城市的天氣情況。它與該應用內其他的活動有相同的親緣關係(默認的應用親緣關係),同時它允許通過這個屬性來重新指定親緣關係。當你的一個活動起動了天氣報告活動,它最初屬於和你的活動相同的任務。然而,當旅行應用的任務來到前臺時,天氣報告活動被重新指派給該任務,並在其內顯示。

提示: 從用戶的角度來看,如果一個.apk文件包含了不止一個"應用程序",你很可能想使用taskAffinity屬性來爲與每個"應用程序"有關聯的活動指派不同的親緣關係。


2.3   清理後退堆棧


如果用戶長時間離開任務,系統會清理掉除根活動以外的所有活動的任務。當用戶再次回到任務時,只有根活動被恢復。系統的這種行爲,是因爲經過一段時間以後,用戶很可能放棄他們之前所做的事情並且等返回任務時開始做新的東西。


這裏有些活動屬性,你可以使用它們來修改上述系統行爲:

alwaysRetainTaskState
在任務的根活動中,如果這個屬性被設置爲"true",剛剛描述的默認行爲則不會發生。任務保留堆棧內所有的活動,即使長時間離開後。

clearTaskOnLaunch
如果在任務的根活動中,將該屬性設置爲 "true",堆棧被向下清理至根活動,無論用戶何時離開任務以及何時返回到它。換句話說,它與alwaysRetainTaskState屬性的作用剛好相反。此時,用戶通常返回到任務的初始狀態,即便是離開任務一小會。

finishOnTaskLaunch
該屬性與clearTaskOnLaunch屬性相似,但是它只在單個活動上執行操作,而不是整個任務。它還可以導致任何活動從任務中移除,包括根活動。當該屬性被設置爲"true"時, 活動依然爲僅對當前會話的任務的一部分。如果用戶離開,然後又返回到任務,則該活動就不再出現了。



2.4  啓動一個任務


你可以通過把一個將"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>

此類意圖過濾器會導致活動的圖標和標籤在應用程序啓動器中顯示,這給用戶一種啓動活動以及在由它創建的任務被啓動後的任何時候返回到該任務的途徑。


這種意圖過濾的第二個能力是重要的:用戶必須能夠離開任務並隨後使用該活動啓動器返回到它。出於這個原因,兩個標記了活動通常作爲用來初始化任務的啓動模式(launchmodes)"singleTask""singleInstance",應該僅在活動具有ACTION_MAINCATEGORY_LAUNCHER過濾器的時候使用。想象一下,例如,如果缺少該過濾器缺時可能會發生什麼:意圖啓動了一個"singleTask"活動,並初始化一個新的任務,接着用戶在這個任務中花費大量時間做了些操作 。然後用戶按Home按鈕離開。現在,該任務進入後臺並不再可見。此時,用戶沒有辦法返回到該任務,因爲它沒有出現在應用程序啓動其中。


對於那些不想讓用戶能夠返回到活動的情況,可以設置<activity>元素的finishOnTaskLaunch屬性爲"true"(參閱清理堆棧章節)



                                                                                                                    

                                                                                                                2012年9月18日,畢




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