Save the date! Android Dev Summit is coming to Sunnyvale, CA on Oct 23-24, 2019.

工作和返回堆疊

一個應用程式通常包含多個 Activity。每個 Activity 應根據使用者可執行的特定動作類型加以設計,且要能啟動其他 Activity。 例如,電子郵件應用程式可能會有一個可顯示新訊息清單的 Activity。 當使用者選擇一則訊息,會開啟新的 Activity 以檢視該訊息。

Activity 甚至可啟動裝置上其他應用程式的 Activity。例如,如果您的應用程式想要傳送一封電子郵件訊息,您可以定義一個意圖以執行「傳送」動作並包含一些資料,像是電子郵件地址和訊息。 其他應用程式中宣告處理此意圖類型的 Activity 就會開啟。 在這種情況下,意圖就是要傳送電子郵件,因此電子郵件應用程式會啟動「撰寫」Activity (如果有多個 Activity 支援相同的意圖,則系統會讓使用者選擇要使用的 Activity)。 電子郵件傳送後,您的 Activity 就會繼續,並將電子郵件 Activity 視為您應用程式的一部分。 雖然 Activity 可能來自不同的應用程式,但 Android 會將兩個 Activity 放在相同的工作中,以維護使用者體驗的流暢性。

工作是執行特定工作時,與使用者互動的 Activity 集合。 Activity 會在堆疊 (返回堆疊) 中依照每個 Activity 開啟的順序加以排列。

裝置主螢幕是大多數工作開始的地方。當使用者輕觸應用程式啟動組件上的某個圖示 (或主螢幕上的捷徑 ) 時,應用程式工作會移到前景。 如果應用程式沒有工作 (最近未使用應用程式),則會建立新的工作,且該應用程式的「主要」Activity 會以堆疊中的根 Activity 形式開啟。

當目前的 Activity 啟動另一個 Activity 時,會將新的 Activity 推到堆疊的頂端並取得焦點。 之前的 Activity 會留在堆疊中,但已停止。Activity 停止後,系統會保留其使用者介面的目前狀態。 當使用者按下 [返回] 按鈕,會將目前的 Activity 從堆疊頂端推出 (Activity 已終結),並繼續進行之前的 Activity (還原其 UI 之前的狀態)。 堆疊中的 Activity 不會重新整理,只會從堆疊推入和推出 — 由目前 Activity 啟動時推入堆疊,而當使用者使用 [返回] 按鈕離開時推出堆疊。 因此,返回堆疊會以「後進先出」的物件結構進行運作。 圖 1 透過時間軸將此行為視覺化,以時間軸顯示 Activity 間的進度以及每個時間點的目前返回堆疊。

圖 1.顯示工作中每個新 Activity 如何將項目新增到返回堆疊。 當使用者按下 [返回] 按鈕,目前的 Activity 將會終結,而之前的 Activity 則會繼續進行。

如果使用者持續按下 [返回],則會持續推出堆疊中的每個 Activity 以顯示之前的 Activity,直到使用者返回主螢幕 (或到工作開始時執行的 Activity)。 當堆疊中的 Activity 全部移除後,工作將不再存在。

圖 2.兩個工作:工作 B 在前景收到使用者互動,而工作 A 在背景等待繼續。

圖 3.單一 Activity 會具現化很多次。

工作是一個緊密結合的單位,當使用者開始新的工作時可以移到「背景」,或透過 [首頁] 按鈕前往主螢幕。 在背景時,工作中的所有 Activity 都會停止,但該工作的返回堆疊會保留下來 — 該工作純粹失去焦點,由另一個工作取而代之,如圖 2 所示。 之後,工作可以返回「前景」,讓使用者繼續未完成的工作。 例如,假設目前的工作 (工作 A) 的堆疊中有三個 Activity — 兩個位於目前的 Activity 下。 使用者按下 [首頁] 按鈕,然後從應用程式啟動新的應用程式。 當主螢幕出現時,工作 A 會移到背景。 新的應用程式啟動時,系統會啟動該應用程式的工作 (工作 B),該應用程式會有自己的 Activity 堆疊。 與該應用程式互動之後,使用者會再次回到首頁,並選取原來啟動工作 A 的應用程式。現在,工作 A 移到了前景 — 堆疊中的三個 Activity 全部保持不變,而堆疊中最頂端的 Activity 則會繼續進行。 此時,使用者也能切換回工作 B,前往首頁並選取啟動該工作的應用程式圖示 (或從總覽畫面選取應用程式工作)。這是在 Android 執行多工作業的範例。

注意:背景可以一次執行多個工作。 不過,如果使用者同時執行多個背景工作,系統可能會開始終結背景 Activity 以復原記憶體,導致 Activity 狀態遺失。 請參閱下列有關 Activity 狀態的章節。

由於返回堆疊中的 Activity 不會重新整理,如果您的應用程式允許使用者從一個以上的 Activity 中啟動特定 Activity,則會建立該 Activity 的新執行個體並推入堆疊 (而不會將 Activity 任何之前的執行個體移到最頂端)。 因此,您應用程式中的一個 Activity 可能會具現化很多次 (甚至來自不同的工作),如圖 3 所示。 也因為這樣,如果使用者使用 [返回] 按鈕瀏覽之前的資訊,Activity 的每個執行個體會依開啟的順序顯示 (每個會有自己的 UI 狀態)。 不過,如果您不希望 Activity 具現化一次以上,則可以修改這個行為。 如需詳細步驟,請參閱下文的管理工作

摘要說明 Activity 和工作的預設行為:

  • 當 Activity A 啟動 Activity B,Activity A 會停止,但系統會保留其狀態(例如捲軸位置和輸入表單的文字)。 如果使用者在 Activity B 按下 [返回] 按鈕,Activity A 的狀態會復原並繼續執行。
  • 當使用者按下 [首頁] 按鈕離開工作,目前的 Activity 會停止且其工作會移到背景。 系統會保留工作中所有 Activity 的狀態。如果使用者稍後選取啟動工作的啟動組件圖示繼續執行工作,工作會移到前景並在堆疊頂端繼續執行 Activity。
  • 如果使用者按下 [返回] 按鈕,會將目前的 Activity 從堆疊推出並終結。 堆疊中之前的 Activity 會繼續進行。Activity 終結後,系統將不會保留 Activity 的狀態。
  • Activity 可以具現化很多次,即使來自其他工作也一樣。

導覽設計

如需應用程式導覽如何在 Android 運作的詳細資訊,請參閱 Android 設計的導覽指南。

儲存 Activity 狀態

如上所述,系統的預設行為會在 Activity 停止時保留 Activity 的狀態。 如此一來,當使用者瀏覽之前的 Activity 時,其使用者介面的顯示方式將與離開時一樣。 不過,您可以 — 也應該 — 使用回呼方法主動保留 Activity 的狀態,以防止 Activity 遭到終結且必須重新建立的情況。

如果系統停止您其中一個 Activity (例如,當新的 Activity 開始或工作移到背景),當系統需要復原系統記憶體時,可能會完全終結該 Activity。 發生這種情況時,與 Activity 狀態相關的資訊都將遺失。如果發生這種情況,系統仍然知道該 Activity 位於返回堆疊中,但是當 Activity 移到堆疊頂端時,系統必須重新建立該 Activity (而不是繼續執行)。 如果不想讓使用者的工作遺失,您應該在 Activity 中實作 onSaveInstanceState() 回呼方法,主動保留該工作。

如需儲存 Activity 狀態的詳細資訊,請參閱 Activity 文件。

管理工作

如上所述,Android 管理工作和返回堆疊的方式 — 將連續啟動的所有 Activity 放在相同的工作及「後進先出」堆疊中 — 對大多數應用程式而言非常好用,而且您不需擔心 Activity 與工作關聯的方式或它們如何存在於返回堆疊中。 不過,您也許會想中斷一般的行為。 您或許會希望應用程式的 Activity 可以在啟動時開始一個新的工作 (而不是放入目前的工作中);或者當您啟動一個 Activity 時,您可能會想使用其現有的執行個體 (而不是在返回堆疊頂端建立新的執行個體);又或者當使用者離開工作時,您想要清除返回堆疊中的所有 Activity,只留下根 Activity。

如要執行這些工作及更多工作,您可以使用 <activity> 宣示說明元素中的屬性,以及您傳送到 startActivity() 之意圖中的旗標。

就這個情況而言,您可以使用的主要 <activity> 屬性包括:

而您可以使用的主要意圖旗標包括:

在以下各節中,您將瞭解如何使用這些宣示說明屬性和意圖旗標,定義 Activity 與工作關聯的方式以及它們在返回堆疊中的行為。

同時,還會分開討論工作與 Activity 如何在總覽畫面表示和進行管理。 請參閱總覽畫面以取得詳細資訊。 一般而言,您應該允許系統定義如何在總覽畫面中呈現工作與 Activity,而且不需要修改此行為。

注意:大多數應用程式不應中斷 Activity 和工作的預設行為: 如果您判斷修改 Activity 的預設行為是必要的,請謹慎小心並記得測試啟動期間 Activity 的可用性,以及使用 [返回] 按鈕從其他 Activity 和工作瀏覽到此 Activity 的情況。請記得測試可能會與使用者預期的行為衝突的瀏覽行為。

定義啟動模式

啟動模式可讓您定義 Activity 的新執行個體與目前工作關聯的方式。 您可用兩種方法定義不同的啟動模式:

因此,如果 Activity A 啟動 Activity B,Activity B 可以在其宣示說明中定義它應如何與目前的工作 (如果有) 關聯,而且 Activity A 也能要求 Activity B 應如何與目前的工作關聯。 如果這兩個 Activity 皆定義 Activity B 應如何與工作關聯,相較於 Activity B 的要求 (如宣示說明中所定義),會優先採用 Activity A 的要求 (如意圖中所定義)。

注意:某些宣示說明檔案中提供的啟動模式可能沒有對應的意圖旗標,同樣地,某些意圖旗標提供的啟動模式無法在宣示說明中定義。

使用宣示說明檔案

當您在宣示說明檔案中宣告 Activity 時,您可以使用 <activity> 元素的 launchMode 屬性,指定 Activity 應如何與工作關聯。

launchMode 屬性可指定應如何將 Activity 啟動至工作內的指示。 您可以為 launchMode 屬性指定四種不同的啟動模式:

"standard" (預設模式)
預設。系統在啟動 Activity 的工作中建立新的執行個體,並將意圖路由至該處。 Activity 可以具現化很多次,每個執行個體可屬於不同的工作,而且一個工作可以有多個執行個體。
"singleTop"
如果 Activity 的執行個體已經出現在目前工作的頂端,系統會透過呼叫其 onNewIntent() 方法,將意圖路由至該執行個體,而不是建立新的 Activity 執行個體。 Activity 可以具現化很多次,每個執行個體可屬於不同的工作,而且一個工作可以有多個執行個體 (但僅限於返回堆疊頂端的 Activity 不是現有的 Activity 執行個體時)。

例如,假設工作的返回堆疊包含根 Activity A 及 Activity B、C 及在最頂端的 D (堆疊為 A-B-C-D;D 在最頂端)。 類型 D Activity 的意圖抵達。 如果 D 有預設的 "standard" 啟動模式,則會啟動新的類別執行個體,且堆疊會變成 A-B-C-D-D。不過,如果 D 啟動模式為 "singleTop",D 的現有執行個體會透過 onNewIntent() 接收意圖,這是因為它位於堆疊的最頂端 — 堆疊會維持 A-B-C-D。不過,如果類型 B Activity 的意圖抵達,則 B 的新執行個體會新增到堆疊中,即使其啟動模式為 "singleTop" 也是如此。

注意:建立新的 Activity 執行個體之後,使用者可按下 [返回] 按鈕,返回之前的 Activity。 但是,如果處理新意圖的是現有的 Activity 執行個體,則使用者無法按下 [返回] 按鈕回到新意圖抵達 onNewIntent() 之前的 Activity 狀態。

"singleTask"
系統會建立新的工作並在新工作的根目錄將 Activity 具現化。不過,如果 Activity 的執行個體已經出現在其他工作中,系統會透過呼叫其 onNewIntent() 方法,將意圖路由至現有的執行個體,而不是建立新的執行個體。 一次只能有一個 Activity 執行個體。

注意:雖然 Activity 是在新工作中啟動,使用者仍可使用 [返回] 按鈕返回之前的 Activity。

"singleInstance"
"singleTask" 一樣,差別在於系統不會將任何其他 Activity 啟動至保留執行個體的工作中。 Activity 一律是其工作的唯一成員;使用此項目啟動的任何 Activity 會在個別的工作中開啟。

另外一個例子,Android 瀏覽器應用程式宣告網頁瀏覽器 Activity 應永遠在自己的工作中開啟 — 透過在 <activity> 元素指定 singleTask 啟動模式。 這表示如果您的應用程式發出開啟 Android 瀏覽器的意圖,其 Activity 不會與您的應用程式放在同一個工作中。 而是會為瀏覽器啟動新的工作,或者如果瀏覽器已經有工作在背景執行,會將該工作帶出來處理新的意圖。

無論 Activity 在新工作啟動或與啟動該 Activity 之 Activity 的相同工作中啟動,使用者都能使用 [返回] 按鈕返回之前的 Activity。 不過,如果您啟動指定 singleTask 啟動模式的 Activity,如果該 Activity 的執行個體存在於背景工作中,則該工作會整個移到前景。 此時,返回堆疊現在包含已帶出且位於堆疊頂端之工作的所有 Activity。 圖 4 說明這種類型的情況。

圖 4.顯示含有啟動模式 "singleTask" 的 Activity 如何新增到返回堆疊。 如果 Activity 已經是背景工作的一部份且有自己的返回堆疊,則整個返回堆疊都會帶出來,位於目前工作的最頂端。

如要進一步瞭解如何使用宣示說明檔案中的啟動模式,請參閱 <activity> 元素,其中會有 launchMode 屬性和可接受值的詳細說明。

注意:您使用 launchMode 屬性指定的 Activity 行為可被啟動 Activity 之意圖所含的旗標所覆寫,如下一節所述。

使用意圖旗標

啟動 Activity 時,您可以在傳送到 startActivity() 的意圖中包含旗標,以修改 Activity 及其工作的預設關聯。 您可以用來修改預設行為的旗標包括:

FLAG_ACTIVITY_NEW_TASK
在新工作中啟動 Activity。如果工作已為您目前啟動的 Activity 執行,該工作會移到前景並復原至上個狀態,而且 Activity 會在 onNewIntent() 收到新的意圖。

這會產生與 "singleTask" launchMode 值相同的行為,如上節所述。

FLAG_ACTIVITY_SINGLE_TOP
如果現在正在啟動的 Activity 是目前的 Activity (位於返回堆疊的頂端),則現有執行個體會收到 onNewIntent() 呼叫,而不會建立新的 Activity 執行個體。

這會產生與 "singleTop" launchMode 值相同的行為,如上節所述。

FLAG_ACTIVITY_CLEAR_TOP
如果正在啟動的 Activity 已在目前的工作中執行,則不會啟動新的 Activity 執行個體,而是會終結位於其上方的所有其他 Activity,且此意圖會透過 onNewIntent() 傳送到繼續執行的 Activity 執行個體 (現在位於頂端)。

沒有任何 launchMode 屬性值可以產生此行為。

FLAG_ACTIVITY_CLEAR_TOP 最常與 FLAG_ACTIVITY_NEW_TASK 搭配使用。 一起使用時,這些旗標可以找出位於其他工作中的現有 Activity,然後將它放置於可以回應意圖的地方。

注意:如果指定 Activity 的啟動模式為 "standard",它也會從堆疊中移除,改為啟動新的執行個體處理傳入的意圖。 這是因為當啟動模式為 "standard" 時,一律會為新的意圖建立新的執行個體。

處理親和性

親和性可指出 Activity 偏好屬於哪個工作。根據預設,相同應用程式的所有 Activity 間互相都有親和性。 因此,根據預設,相同應用程式的所有 Activity 都偏好位於相同的工作。 不過,您可以修改 Activity 的預設親和性。 不同應用程式中定義的 Activity 可以共用親和性,或者相同應用程式中定義的 Activity 可以指派不同的工作親和性。

您可以使用 &lt;activity&gt; 元素的 taskAffinity 屬性修改任何指定 Activity 的親和性。

taskAffinity 屬性使用字串值,但必須與 &lt;manifest&gt; 元素中宣告的預設封裝名稱不同,因為系統使用該名稱來識別應用程式的預設工作親和性。

在兩種情況下會用到親和性:

  • 當啟動 Activity 的意圖包含 FLAG_ACTIVITY_NEW_TASK 旗標。

    根據預設,新的 Activity 會啟動至 Activity (名為 startActivity()) 的工作中。 系統會將它推入至與呼叫端相同的返回堆疊。 不過,如果傳送至 startActivity() 的意圖包含 FLAG_ACTIVITY_NEW_TASK 旗標,則系統會找尋不同的工作來放置新的 Activity。 這通常是新工作。 不過,它不一定要是新工作。如果現有工作中有與新 Activity 相同的親和性,Activity 會啟動至該工作中。 如果沒有,會開始新的工作。

    如果此旗標導致 Activity 開始新的工作,而使用者按 [首頁] 按鈕離開它,必須要有方法可以讓使用者回來瀏覽這個工作。 有些實體 (例如,通知管理員) 總是從外部工作開始 Activity,從來不使用自己的工作,因此他們都會將 FLAG_ACTIVITY_NEW_TASK 放入傳送到 startActivity() 的意圖。 如果您的 Activity 可以由外部實體呼叫且可能使用此旗標,記得要提供使用者獨立的方法回到啟動的工作,例如,透過啟動組件圖示 (工作的根 Activity 有一個 CATEGORY_LAUNCHER 意圖篩選器;請參閱下方的開始工作)。

  • 當 Activity 的 allowTaskReparenting 屬性設為 "true"

    在這種情況下,當工作移到前景時,Activity 可以從其啟動的工作移到與其有親和性的工作。

    例如,假設將報告所選城市天氣狀況的 Activity 定義為旅遊應用程式的一部份。 它與相同應用程式中的其他 Activity 有相同的親和性 (預設的應用程式親和性),而且它允許與此屬性重設父代。 當您的其中一個 Activity 開始氣象報告程式 Activity,它一開始屬於與您 Activity 相同的工作。 不過,當旅遊應用程式工作移到前景,氣象報告程式 Activity 就會重新指派給該工作,並在其中顯示。

提示:如果從使用者的角度來看 .apk 檔案包含一個以上的「應用程式」,您可能會想要使用 taskAffinity 屬性對與每個「應用程式」關聯的 Activity 指派不同的親和性。

清除返回堆疊

如果使用者離開工作一段很長的時間,系統會清除根 Activity 以外所有 Activity 的工作 。當使用者再次回到工作,只會復原根 Activity。 系統會有這樣的行為是因為在一段很長的時間後,使用者很可能會放棄他們之前在做的工作,並回到工作開始其他新的工作。

您可以使用下列一些 Activity 屬性來修改這個行為:

alwaysRetainTaskState
如果這項屬性在工作的根 Activity 中設為 "true",則剛描述的預設行為不會發生。 即使過了很長的一段時間,工作仍然會在堆疊保留所有的 Activity。
clearTaskOnLaunch
如果這項屬性在工作的根 Activity 中設為 "true",則剛描述的預設行為不會發生。 即使過了很長的一段時間,工作仍然會在堆疊保留所有的 Activity。 換句話說,它與 alwaysRetainTaskState 相反。即便使用者只離開工作一小段時間,使用者還是會回到工作的起始狀態。
finishOnTaskLaunch
這項屬性與 clearTaskOnLaunch 相似,但它在單一 Activity 上作業,而不是在整個工作。 它也會導致任何 Activity 離開,包含根 Activity。 如果設成 "true",Activity 只會在目前的工作階段留在此工作中。 如果使用者離開後再回到工作,該工作將不再存在。

開始工作

您可以給予 Activity 一個意圖篩選器,將 "android.intent.action.MAIN" 設定為指定的動作, "android.intent.category.LAUNCHER" 設定為指定的類別,以便將該 Activity 設定為工作的進入點。 例如:

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

這類意圖篩選器可在應用程式啟動組件顯示 Activity 的圖示和標籤,讓使用者啟動 Activity 並回到 Activity 啟動後任何時間建立的工作。

第二項功能很重要:使用者必須能夠在離開工作後,使用此 Activity 啟動組件回到此工作。 由於這個原因,兩個將 Activity 標示為一律啟動工作的啟動模式 "singleTask""singleInstance",應只能在 Activity 有 ACTION_MAINCATEGORY_LAUNCHER 篩選器時才能使用。 例如,試想如果缺少篩選器會發生什麼情況: 意圖會啟動 "singleTask" Activity、起始新工作,然後使用者會花一些時間在該工作進行作業。 之後,使用者按下 [首頁] 按鈕。 此工作現在會傳送到背景而且不會顯示。現在,使用者沒有辦法回到工作,這是因為應用程式啟動組件沒有代表此工作的項目。

在您不希望使用者返回 Activity 的情況下,將 <activity> 元素的 finishOnTaskLaunch 設定為 "true" (請參閱清除堆疊)。

如要進一步瞭解工作和 Activity 在總覽畫面中的顯示及管理方式,請參閱總覽畫面