Android開發者指南-Task 和 Back Stack[原創譯文]

activity:

Task and Back Stack

英文原文:http://developer.android.com/guide/topics/fundamentals/tasks-and-back-stack.html


快速查看
  • 所有的 activity 都屬於一個task
  • task 包含了 activity 的集合,並按照用戶使用的順序排列
  • task 可以轉入後臺,並會保存每個 activity 的狀態,這樣用戶運行其它 task 時就不會丟失當前的工作了。
在本文中
  1. 保存 Activity 狀態
  2. 管理多個 Task
    1. 定義啓動模式
    2. 處理 affinity
    3. 清理 back stack
    4. 啓動 task
相關文章
  1. Multitasking the Android Way
參閱
  1. Android 設計 : 導航
  2. 應用程序的生命週期視頻
  3. <activity> manifest 元素

一個應用程序通常包含多個 activity。 每個 activity 在設計時都應該以執行某個用戶發起的 action 作爲核心目標,並且它能啓動其它 activity。 比如,一個 email 應用可能會用一個 activity 來列出所有的新 email,當用戶選中一封 email 時,再打開一個新的 activity 來顯示這封 email。

一個 activity 甚至可能會啓動另一個應用中的 activity。 比如,如果你的應用需要發送 email,你可以定義一個 intent 來執行“send” action,其中包含一些數據,如 email 地址、正文等。 然後會打開一個其它應用中已聲明能夠處理這類 intent 的 activity。 這裏是一個發送 email 的 intent,所以會打開一個 email 應用的“新建郵件”activity(如果有多個 activity 都支持同一個 intent,則系統或讓用戶選擇一個打開)。 email 發送完畢後,你的 activity 將會恢復,看起來 email activity 就像是你的應用中的一部分一樣。 雖然這兩個 activity 可能來自不同的應用,通過把它們放入同一個task,Android 保證了無縫的用戶體驗。

task 是多個 activity 的集合,用戶進行操作時將與這些 activity 進行交互。 這些 activity 按照啓動順序排隊存入一個棧(即“back stack”)。

大部分 task 都啓動自 Home 屏幕。當用戶觸摸 application launcher 中的圖標(或 Home 屏幕上的快捷圖標)時,應用程序的 task 就進入前臺。 如果該應用不存在 task(最近沒有使用過此應用),則會新建一個 task,該應用的“main”activity 作爲棧的根 activity 被打開。

當用戶返回到 home屏幕執行另一個 task 時,一個 task 被移動到後臺執行,此時它的返回棧(back stack)也被保存在後臺, 同時 android 爲新 task 創建一個新的返回棧(back stack),當它被再次運行從而返回前臺時,它的返回棧(back stack)被移到前臺,並恢復其之前執行的activity,如下圖所示。 如果後臺有太多運行 task ,系統將會殺死一些 task 釋放內存。

如果當前 activity 啓動了另一個 activity,則新的 activity 被壓入棧頂並獲得焦點。 前一個 activity 仍保存在棧中,但是被停止。activity 停止時,系統會保存用戶界面的當前狀態。 當用戶按下返回鍵,則當前 activity 將從棧頂彈出(被銷燬),前一個 activity 將被恢復(之前的用戶界面狀態被恢復)。 activity 在棧中的順序永遠不會改變,只會壓入和彈出——被當前 activity 啓動時壓入棧頂,用戶用返回鍵離開時彈出。 這樣,back stack 以“後進先出”的方式運行。圖1 以時間線的方式展示了多個 activity 切換時對應當前時間點的 back stack 狀態。

Back Stack 狀態

圖 1. 如圖所示, task 中的每個新 activity 都會相應在 back stack 中增加一項。當用戶按下返回鍵時,當前 activity 被銷燬,前一個 activity 被恢復。

如果用戶不停地按下返回鍵,則棧中每個 activity 都會依次彈出,並顯示前一個 activity,直至用戶回到 Home 屏幕(或者任一啓動該 task 的 activity)。 當所有 activity 都從棧中彈出後, task 就此消失。

多個 task

圖 2. 兩個 task :Task B 在前臺與用戶交互,而 Task A 在後臺等待喚醒。

多個實例

圖 3. activity A 被實例化多次。

一個 task (task)是一個整體單位, 當用戶啓動一個新的 task 或者回到 Home 屏幕時,這個 task 就轉爲“後臺”。 當 task 處於後臺時,其中所有的 activity 都處於停止狀態,但是這個 task 的 back stack 仍然完整保留——如圖2所示,在其它 task 獲得焦點期間,這個 task 只是失去焦點而已。 task 可以返回“前臺”,所以用戶能夠自離開的地方繼續工作。比如,假定當前 task (Task A)的棧中共有3個 activity —— 下面有兩個 activity。 這時,用戶按下Home鍵,然後從 application launcher 中啓動一個新的應用。當 Home 屏幕出現時,Task A 進入後臺。 新的應用啓動時,系統會爲它開啓一個 task (Task B),其中放入新應用中的 activity。 用完這個應用後,用戶再次返回 Home 屏幕,並選中那個啓動 Task A 的應用。 現在,Task A 進入前臺——棧中的三個 activity 仍然完好,位於最頂部的 activity 恢復運行。 這時,用戶仍然可以切回 Task B,通過回到 Home 屏幕並選擇相應圖標即可(或者觸摸並按住Home鍵調出最近 task 列表並選中)。 以下是 Android 多 task 的實例。

注意: 可以在後臺同時保留多個 task 。但是,假如用戶同時運行着多個後臺 task ,則系統可能會銷燬後臺 activity 以便騰出內存,這會導致 activity 狀態的丟失。 請參閱 Activity 狀態

因爲 back stack 中的 activity 順序永遠不會改變,如果應用允許某個 activity 可以讓用戶啓動多次,則新的實例會壓入棧頂(而不是打開之前位於棧頂的 activity)。 於是,一個 activity 可能會初始化多次(甚至會位於不同的 task 中),如圖3所示。 如果用戶用回退鍵回退時,activity 的每個實例都會按照原來打開的順序顯示出來(用戶界面也都按原來狀態顯示)。 當然,如果你不想讓 activity 能被多次實例化,你可以改變它。方法在後續章節 管理多個 Task 中討論。

下面把 activity 和 task 的默認特性彙總一下:

  • 當 Activity A 啓動 Activity B 時,Activity A 被停止,但系統仍會保存狀態(諸如滾動條位置和 form 中填入的文字)。 如果用戶在 Activity B 中按下回退鍵時,Activity A 恢復運行,狀態也將恢復。
  • 當用戶按下Home鍵離開 task 時,當前 activity 停止, task 轉入後臺。 系統會保存 task 中每個 activity 的狀態。如果用戶以後通過選中啓動該 task 的圖標來恢復 task , task 就會回到前臺,棧頂的 activity 會恢復運行。
  • 如果用戶按下回退鍵,當前 activity 從棧中彈出,並被銷燬。 棧中前一個 activity 恢復運行。 當 activity 被銷燬時,系統不會保留 activity 的狀態。
  • activity 可以被實例化多次,甚至可以位於不同的 task 中。

導航設計

關於 Android 中應用程序間導航的詳情,請參閱 Android 導航 設計指南。

保存 Activity 狀態

如上所述,系統默認會在 activity 停止時保存其狀態。這樣,當用戶返回時,用戶的界面能與離開時顯示得一樣。 不過,你可以——也應該——用回調方法主動地保存 activity 的狀態,以便應對 activity 被銷燬並重建的情況。

當停止 activity 時(比如啓動了一個新 activity 或者 task 轉入後臺),處於騰出內存的需要,系統也許會完全銷燬該 activity。 這樣 activity 的狀態信息將會丟失。如果發生這種情況,系統仍然是知道該 activity 已經置入 back stack 的。 但是當它位於棧頂時,系統必須重建它(而不是恢復)。爲了防止用戶工作內容的丟失,你應該主動保存這些內容,通過實現 activity 的 onSaveInstanceState() 回調方法即可。

關於如何保存 activity 狀態的詳情,請參閱 Activities

管理多個 Task

如上所述——把所有已啓動的 activity 相繼放入同一個 task 中以及一個“後入先出”棧, Android 管理 task 和 back stack 的這種方式適用於大多數應用, 你也不用去管你的 activity 如何與 task 關聯及如何彈出 back stack 的。 不過,有時你也許決定要改變這種普通的運行方式。 也許你想讓某個 activity 啓動一個新的 task (而不是被放入當前 task ); 或者,你想讓 activity 啓動時只是調出已有的某個實例(而不是在 back stack 頂創建一個新的實例); 或者,你想在用戶離開 task 時只保留根 activity,而 back stack 中的其它 activity 都要清空,

你能做的事情還有很多,利用 <activity> manifest 元素的屬性和傳入 startActivity() 的 intent 中的標誌即可。

這裏,你可以使用的 <activity> 屬性主要有:

可用的 intent 標誌主要有:

在下一節,你可以看到如何利用這些 manifest 屬性和 intent 標誌來定義 activity 與 task 的關聯性,以及 back stack 的工作方式。

警告: 大多數應用不應該改變 activity 和 task 默認的工作方式。 如果你確定有必要修改默認方式,請保持謹慎,並確保 activity 在啓動和從其它 activity 用回退鍵返回時的可用性。 請確保對可能與用戶預期的導航方式相沖突的地方進行測試。

定義啓動模式

啓動模式定義了一個新的 activity 實例與當前 task 的關聯方式。定義啓動模式的方法有兩種:

這樣,如果 Activity A 啓動了Activity B,則 Activity B 可以在 manifest 中定義它如何與當前 task 關聯(如果存在的話), 並且,Activity A 也可以要求 Activity B 與當前 task 的關聯關係。 如果兩者都定義了,則 Activity A 的請求(intent 中定義)優先於 Activity B 的定義(在 manifest 中)。

注意: manifest 文件中的某些啓動模式在 intent 標誌中並不可用,反之亦然,intent 中的某些模式也無法在 manifest 中定義。

使用 manifest 文件

在 manifest 文件中聲明 activity 時,你可以利用 <activity> 元素的 launchMode 屬性來設定 activity 與 task 的關係。

launchMode 屬性指明瞭 activity 啓動 task 的方式。 launchMode 屬性可設爲四種啓動模式:

"standard" (默認模式)
默認值。系統在啓動 activity 的 task 中創建一個新的 activity 實例,並把 intent 傳送路徑指向它。 該 activity 可以被實例化多次,各個實例可以屬於不同的 task,一個 task 中也可以存在多個實例。
"singleTop"
如果 activity 已經存在一個實例並位於當前 task 的棧頂,則系統會調用已有實例的 onNewIntent() 方法把 intent 傳遞給已有實例,而不是創建一個新的 activity 實例。 activity 可以被實例化多次,各個實例可以屬於不同的 task,一個 task 中可以存在多個實例(但僅當 back stack 頂的 activity 實例不是該 activity 的)。

比如,假定 task 的 back stack 中包含了根 activity A 和 activities B、C、D(順序是 A-B-C-D;D 在棧頂)。 這時過來一個啓動 D 的 intent。 如果 D 的啓動模式是默認的"standard",則會啓動一個新的實例,棧內容變爲 A-B-C-D-D。 但是,如果 D 的啓動模式是"singleTop",則已有的 D 實例會通過 onNewIntent() 接收這個 intent,因爲該實例位於棧頂——棧中內容仍然維持 A-B-C-D 不變。 當然,如果 intent 是要啓動 B 的,則 B 的一個新實例還是會加入棧中,即使 B 的啓動模式是"singleTop"也是如此。

注意: 一個 activity 的新實例創建完畢後,用戶可以按回退鍵返回前一個 activity。 但是當 activity 已有實例正在處理剛到達的 intent 時,用戶無法用回退鍵回到 onNewIntent() 中 intent 到來之前的 activity 狀態。
"singleTask"
系統將創建一個新的 task,並把 activity 實例作爲根放入其中。 但是,如果 activity 已經在其它 task 中存在實例,則系統會通過調用其實例的 onNewIntent() 方法把 intent 傳給已有實例,而不是再創建一個新實例。 此 activity 同一時刻只能存在一個實例。
注意: 雖然 activity 啓動了一個新的 task,但用戶仍然可以用回退鍵返回前一個 activity。
"singleInstance"
除了系統不會把其它 activity 放入當前實例所在的 task 之外,其它均與"singleTask"相同。 activity 總是它所在 task 的唯一成員;它所啓動的任何 activity 都會放入其它 task 中。

舉個例子,Android 的瀏覽器應用就把 web 瀏覽器 activity 聲明爲總是在它自己獨立的 task 中打開——把<activity> 設爲singleTask模式。 這意味着,如果你的應用提交 intent 來打開 Android 的瀏覽器,則其 activity 不會被放入你的應用所在的 task 中。 取而代之的是,或是爲瀏覽器啓動一個新的 task;或是瀏覽器已經在後臺運行,只要把 task 調入前臺來處理新 intent 即可。

無論 activity 是在一個新的 task 中啓動,還是位於其它已有的 task 中,用戶總是可以用回退鍵返回到前一個 activity 中。 但是,如果你啓動了一個啓動模式設爲singleTask的 activity,且有一個後臺 task 中已存在實例的話,則這個後臺 task 就會整個轉到前臺。 這時,當前的 back stack 就包含了這個轉入前臺的 task 中所有的 activity,位置是在棧頂。 圖 4 就展示了這種場景。

singleTask 模式下的多 activity 棧

圖 4. 啓動模式爲“singleTask”的 activity 如何加入 back stack 的示意。 如果 activity 已經是在後臺 task 中並帶有自己的 back stack,則整個後臺 back stack 都會轉入前臺,並放入當前 task 的棧頂。

關於在 manifest 文件中使用啓動模式的詳情,請參閱 <activity> 元素文檔,其中詳細描述了launchMode屬性及其可用值。

注意: 你用 launchMode 屬性爲 activity 設置的模式可以被啓動 activity 的 intent 標誌所覆蓋,這將在下一節中描述。

使用 Intent 標誌

在要啓動 activity 時,你可以在傳給 startActivity() 的 intent 中包含相應標誌,以便修改 activity 與 task 的默認關係。 這個標誌可以修改的默認模式包括:

FLAG_ACTIVITY_NEW_TASK
在新的 task 中啓動 activity。 如果要啓動的 activity 已經運行於某 task 中,則那個 task 將調入前臺,最後保存的狀態也將恢復,activity 將在 onNewIntent() 中接收到這個新 intent。

這個過程與前一節所述的"singleTask" launchMode 模式值相同。

FLAG_ACTIVITY_SINGLE_TOP
如果要啓動的 activity 就是當前 activity(位於back stack 頂),則已存在的實例將接收到一個 onNewIntent()調用,而不是創建一個 activity 的新實例。

這個過程與前一節所述的 "singleTop" launchMode 模式值相同。

FLAG_ACTIVITY_CLEAR_TOP
如果要啓動的 activity 已經在當前 task 中運行,則不再啓動一個新的實例,且所有在其上面的 activity 將被銷燬, 然後通過 onNewIntent() 傳入 intent 並恢復 activity(不在棧頂)的運行,

此種模式在launchMode 中沒有對應的屬性值。

FLAG_ACTIVITY_CLEAR_TOP 常與 FLAG_ACTIVITY_NEW_TASK 一起使用。 這表示先定位其它 task 中已存在的 activity,再把它放入可以響應 intent 的位置。

注意: 如果 activity 的啓動模式配置爲"standard",它會先被移除出棧,再創建一個新的實例來處理這個 intent。 這是因爲啓動模式爲 "standard" 時,總是會創建一個新的實例。

處理 affinity

affinity 表示 activity 預期所處的 task 。 缺省情況下,同一個應用中的所有 activity 都擁有同一個 affinity 值。 因此,同一個應用中的所有 activity 默認都期望位於同一個 task 中。 不過,你可以修改 activity 默認的 affinity 值。 不同應用中的 activity 可以共享同一個 affinity 值,同一個應用中的 activity 也可以賦予不同的 task affinity 值。

你可以用 <activity> 元素的 taskAffinity 屬性修改 activity 的 affinity,

taskAffinity 屬性是一個字符串值,必須與 <manifest> 元素定義的包名稱保證唯一性,因爲系統把這個包名稱用於標識應用的默認 task affinity 值。

affinity 將在以下兩種情況下發揮作用:

  • 當啓動 activity 的 intent 包含了 FLAG_ACTIVITY_NEW_TASK 標誌。

    默認情況下,一個新的 activity 將被放入調用 startActivity() 的 activity 所在 task 中,且壓入調用者所處的 back stack 頂。 不過,如果傳給 startActivity() 的 intent 包含了 FLAG_ACTIVITY_NEW_TASK 標誌,則系統會查找另一個 task 並將新 activity 放入其中。 這時經常會新開一個任務,但並非一定如此。 如果一個已有 task 的 affinity 值與新 activity 的相同,則 activity 會放入該 task。 如果沒有,則會新建一個新 task。

    如果本標誌使得 activity 啓動了一個新的 task,用戶按下 Home 鍵離開時,必須採取一些措施讓用戶能回到此 task。 某些應用(比如通知管理器)總是讓 activity 放入其它 task 中啓動,而不是放入自己的 task 中。 因此,它們總是把 FLAG_ACTIVITY_NEW_TASK 標誌置入傳給 startActivity() 的 intent 中。如果你的 activity 可以被外部應用帶此標誌來啓動,請注意用戶會用其它方式返回啓動 task,比如通過應用圖標(task 的根 activity 帶有一個 CATEGORY_LAUNCHER intent 過濾器;參閱下節 啓動 task)。

  • 當一個 activity 的 allowTaskReparenting 屬性設爲 "true"

    這種情況下,當某個 task 進入前臺時,activity 的 affinity 值又與其相同,則它可以從啓動時的 task 移入這個 task 中。

    比如,假定某旅遊應用中有一個 activity 根據所選的城市來報告天氣情況。 它的 affinity 與同一應用中的其它 activity 一樣(整個應用默認的 affinity),且它允許重新指定此屬性的歸屬。 當你的另一 activity 啓動此天氣報告 activity 時,它會在同一個 task 中啓動。 然而,當旅遊應用的 task 進入前臺時,則天氣報告 activity 將會重新放入其 task 中並顯示出來。

提示: 如果一個 .apk 文件中包含了多個“application”,你可能需要用 taskAffinity 屬性來指定每個“application”中 activity 的 affinity 值。

清理 back stack

如果用戶長時間離開某個 task,系統將會僅保留一個根 activity,而把其它 activity 都清除掉。 當用戶返回 task 時,只有根 activity 會被恢復。 系統之所以這麼處理,是因爲經過了很長時間後,用戶是要放棄之前進行的工作,返回 task 是爲了開始新的工作。

你可以利用 activity 的某些屬性來改變這種方式:

alwaysRetainTaskState
如果 task 中根 activity 的此屬性設爲 "true" ,則默認的清理方式不會進行。 即使過了很長時間,task 中所有的 activity 也都會保留在棧中。
clearTaskOnLaunch
如果 task 中根 activity 的此屬性設爲 "true",則只要用戶離開並再次返回該 task,棧就會被清理至根 activity。 也就是說,正好與 alwaysRetainTaskState 相反。用戶每次返回 task 時看到的都是初始狀態,即使只是離開一會兒。
finishOnTaskLaunch
此屬性類似於 clearTaskOnLaunch ,只是它只對一個 activity 有效,不是整個 task。 這能讓任何一個 activity 消失,包括 根 activity。 如果 activity 的此屬性設爲 "true",則只會保留 task 中當前 session 所涉及的內容。 如果用戶離開後再返回 task,它就不存在了。

啓動 task

你可以把某個 activity 設爲 task 的入口,通過發送一個 action 爲 "android.intent.action.MAIN"、category 爲 "android.intent.category.LAUNCHER" 的 intent即可。 比如:

<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>

這種 intent 過濾器將會讓此 activity 的 icon 和 label 作爲應用啓動圖標來顯示,用戶可以啓動此 activity,並且在之後任何時候返回其啓動時的 task。

後一個能力非常重要:用戶必須能離開一個 task ,之後能再回來使用這個啓動 task 的 activity。 因此,標明 activity 每次都會啓動 task 的這兩種 啓動模式: "singleTask" 和 ""singleInstance" 應該僅對帶有ACTION_MAIN 和 CATEGORY_LAUNCHER 過濾器的 activity 才能使用。 想象一下,如果未給出過濾器時會發生什麼:某個 intent 啓動了一個 "singleTask" activity, 並新建了一個 task,用戶在此 task 中工作了一段時間。然後他按了 Home 鍵。 tassk 就轉入後臺,變爲不可見狀態。這時用戶就無法再回到 task 了,因爲它在 application launcher 中沒有相應的條目了。

對於那些不想讓用戶返回的 activity,把 <activity> 元素的 finishOnTaskLaunch 設爲 "true" 即可(參閱清理 back stack


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