1. 程式人生 > >Android官方文件—APP元件(Activities)(Tasks and Back Stack)

Android官方文件—APP元件(Activities)(Tasks and Back Stack)

任務和回退堆疊

應用程式通常包含多個Activity。每個Activity都應圍繞使用者可以執行的特定操作進行設計,並可以啟動其他Activity。例如,電子郵件應用程式可能有一個Activity來顯示新訊息列表。當用戶選擇Activity時,將開啟一個新Activity以檢視該訊息。

Activity甚至可以啟動裝置上其他應用程式中存在的Activity。例如,如果您的應用程式想要傳送電子郵件,您可以定義執行“傳送”Action的意圖幷包含一些資料,例如電子郵件地址和訊息。然後開啟另一個宣告自己處理此類意圖的應用程式的Activity。在這種情況下,目的是傳送電子郵件,因此電子郵件應用程式的“撰寫”Activity啟動(如果多個活動支援相同的意圖,則系統允許使用者選擇使用哪個)。傳送電子郵件後,您的Activity將resume,似乎電子郵件Activity是您的應用程式的一部分。儘管Activity可能來自不同的應用程式,但Android通過將兩個Activity保持在同一任務中來維護這種無縫的使用者體驗。

任務是使用者在執行特定作業時與之互動的Activity的集合。Activity按堆疊(回退堆疊)排列,按每個活動開啟的順序排列。 

裝置主螢幕是大多數任務的起始位置。當用戶觸控應用程式啟動器中的圖示(或主螢幕上的快捷方式)時,該應用程式的任務將到達前臺。如果應用程式不存在任務(最近未使用該應用程式),則會建立一個新任務,該應用程式的“主”活動將作為堆疊中的根Activity開啟。

當前Activity從另一個Activity啟動時,新Activity將被推到堆疊頂部並獲得焦點。之前的Activity仍在堆疊中,但已停止。當Activity停止時,系統將保留其使用者介面的當前狀態。當用戶按下“返回”按鈕時,當前活動將從堆疊頂部彈出(活動被銷燬),之前的Activity將恢復(其UI的先前狀態將恢復)。堆疊中的Activity永遠不會重新排列,只能在當前活動啟動時壓入到堆疊中,並在使用者使用“返回”按鈕離開時彈出。因此,回退堆疊作為“後進先出”物件結構。圖1顯示了這種行為,時間軸顯示了Activity之間的轉換過程以及每個時間點的當前回退棧。

圖1.表示任務中的每個新Activity如何將 其新增到回退堆疊。當用戶按下“返回”按鈕時,將銷燬當前Activity並恢復先前的Activity。

如果使用者繼續按Back,則彈出堆疊中的每個Activity以顯示前一個Activity,直到使用者返回主螢幕(或任務開始時執行的任何Activity)。從堆疊中刪除所有Activity後,該任務不再存在。

圖2.兩個任務:任務B在前臺接收使用者互動, 而任務A在後臺,等待恢復。

任務是一個組合單元,當用戶開始新任務或通過主頁按鈕進入主螢幕時,可以移動到“後臺”。在後臺,任務中的所有Activity都會停止,但任務的後臺堆疊保持不變 - 任務只是失去了焦點,如圖2所示。然後任務可以返回到“前臺“所以使用者可以從中斷的地方繼續前進。例如,假設當前任務(任務A)在其堆疊中有三個Activity - 在當前Activity之下有兩個Activity。使用者按下Home按鈕,然後從應用程式啟動器啟動新應用程式。出現主螢幕時,任務A進入後臺。當新應用程式啟動時,系統會使用自己的一系列Activity為該應用程式(任務B)啟動任務。在與該應用程式互動之後,使用者再次返回Home並選擇最初啟動任務A的應用程式。現在,任務A到達前臺 - 其堆疊中的所有三個Activity都是完整的,並且堆疊頂部的Activity將恢復。此時,使用者還可以通過返回主頁並選擇啟動該任務的應用程式圖示(或從概覽螢幕中選擇應用程式的任務)切換回任務B.這是Android上的多工處理的一個示例。

注意:可以在後臺同時儲存多個任務。但是,如果使用者同時執行許多後臺任務,系統可能會開始銷燬後臺Activity以恢復記憶體,從而導致Activity狀態丟失。請參閱以下有關Activity狀態的部分。

圖3.單個Activity多次例項化

由於後備堆疊中的Activity永遠不會重新排列,如果您的應用程式允許使用者從多個Activity啟動特定Activity,則會建立該活動的新例項並將其推送到堆疊(而不是引入任何先前的Activity例項)到頂部)。因此,應用程式中的一個Activity可能會被多次例項化(甚至來自不同的任務),如圖3所示。因此,如果使用者使用“返回”按鈕向後導航,則Activity的每個例項都按順序顯示被開啟(每個都有自己的UI狀態)。但是,如果您不希望多次例項化Activity,則可以修改此行為。有關如何執行此操作將在後面的“管理任務”一節中討論。

Activity和任務的預設行為總結:

  • 當Activity A啟動Activity B時,Activity A停止,但系統保留其狀態(例如滾動位置和輸入到表單中的文字)。如果使用者在Activity B中按下“返回”按鈕,則Activity A將恢復其狀態。
  • 當用戶通過按Home鍵離開任務時,當前Activity 將停止,其任務將進入後臺。系統保留任務中每個活動的狀態。如果使用者稍後通過啟動器圖示來恢復任務,則任務將到達前臺並恢復堆疊頂部的Activity。
  • 如果使用者按下“返回”按鈕,則會從堆疊中彈出當前Activity並將其銷燬。堆疊中先前的活動被恢復。當活動被銷燬時,系統不會保留Activity的狀態。
  • Activity可以多次例項化,甚至可以從其他任務例項化。

Navigation Design

For more about how app navigation works on Android, read Android Design's Navigation guide。

儲存Activity狀態

如上所述,系統的預設行為會在Activity停止時保留Activity的狀態。這樣,當用戶導航回上一個Activity時,其使用者介面就會以他們離開時的樣子顯示。但是,如果Activity被銷燬並且必須重新建立,則可以應該主動使用回撥方法保留Activity的狀態。

當系統停止您的某個Activity時(例如,當新Activity啟動或任務移至後臺時),如果需要恢復系統記憶體,系統可能會完全銷燬該Activity。發生這種情況時,有關Activity狀態的資訊將丟失。如果發生這種情況,系統仍然知道Activity在回退堆疊中有一個位置,但是當Activity被帶到堆疊頂部時,系統必須重新建立它(而不是恢復它)。為了避免丟失使用者的工作,您應該通過在Activity中實現onSaveInstanceState()回撥方法來主動保留它。

有關如何儲存活動狀態的詳細資訊,請參閱“Activities”文件。

管理任務

如上所述,Android管理任務和後臺堆疊的方式 - 將所有Activity連續放入同一任務和“後進先出”堆疊 - 對大多數應用程式都很有用,你不必擔心關於您的Activity如何與任務相關聯或它們如何存在於後臺堆疊中。但是,您可能決定要中斷正常行為。也許您希望應用程式中的Activity在啟動時開始新任務(而不是放在當前任務中);或者,當你開始一個Activity時,你想要使用它的現有例項(而不是在後面的堆疊頂部建立一個新的例項);或者,您希望在使用者離開任務時清除回退堆疊中除了根Activity之外的所有Activity。

您可以使用<activity>清單元素中的屬性,以及修改傳遞給startActivity()的intent中的flag值執行這些操作以及更多操作。

這裡,在<activity>中您可以使用的主要屬性是:

您可以使用的主要意圖flag是:

在以下部分中,您將瞭解如何使用這些清單屬性和意圖標誌來定義Activity與任務的關聯方式以及它們在回退堆疊中的行為方式。

另外,單獨討論的是如何在概覽螢幕中表示和管理任務和Activity的注意事項。有關詳細資訊,請參閱概述螢幕,您應該允許系統在概覽螢幕中定義您的任務和Activity的顯示方式,而不需要修改這些行為。

警告:大多數應用程式不應該中斷Activity和任務的預設行為。如果您確定您的Activity需要修改預設行為,請謹慎使用,並確保在啟動期間以及使用“返回”按鈕從其他Activity和任務導航回Activity時預測Activity的可用性。請務必檢查可能與使用者預期行為衝突的導航行為。

啟動模式的定義

啟動模式允許您定義Activity的新例項與當前任務的關聯方式。您可以通過兩種方式定義不同的啟動模式:

  • 使用清單檔案

在清單檔案中宣告Activity時,可以指定Activity在啟動時應如何與任務關聯。

  • 使用意圖flag

當您呼叫startActivity()時,您可以在Intent中包含一個flag,該標誌宣告新Activity應如何(或是否)與當前任務關聯。

因此,如果Activity A啟動Activity B,Activity B可以在其清單中定義它應該如何與當前任務相關聯(如果有的話),Activity A也可以請求Activity B應該如何與當前任務相關聯。如果兩個Activity都定義了Activity B應該如何與任務相關聯,則遵循活動B的請求(如其清單中所定義)。

注意:清單檔案可用的某些啟動模式不可用作intent的標誌,同樣,某些啟動模式可用作intent的標誌,無法在清單中定義。

使用清單檔案

在清單檔案中宣告Activity 時,您可以使用<activity> 元素的launchMode屬性指定Activity 應如何與任務關聯。

launchMode屬性指定有關如何將Activity 啟動到任務的說明。您可以為launchMode屬性分配四種不同的啟動模式:

"standard" (the default mode)

預設。系統在啟動它的任務中建立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,則即使其啟動模式為“singleTop”,也會將新的B例項新增到堆疊中。

注意:建立Activity的新例項時,使用者可以按“返回”按鈕返回上一個Activity。但是,當Activity的現有例項處理新意圖時,在新意圖到達onNewIntent()之前,使用者無法按“返回”按鈕返回Activity之前的狀態。

"singleTask"

系統建立新任務並在新任務的根目錄下例項化Activity。但是,如果Activity的例項已存在於單獨的任務中,則系統會通過呼叫其onNewIntent()方法將意圖路由到現有例項,而不是建立新例項。一次只能存在一個Activity例項。

注意:雖然活動在新任務中啟動,但“後退”按鈕仍會將使用者返回到上一個Activity。

"singleInstance"

與“singleTask”相同,但系統不會在持有例項的任務中啟動任何其他Activity。Activity始終是其任務的唯一成員;任何由此開始的Activity都在一個單獨的任務中開啟。

另一個示例,Android瀏覽器應用程式宣告Web瀏覽器Activity應始終在其自己的任務中開啟 - 通過在<activity>元素中指定singleTask啟動模式。這意味著,如果您的應用程式發出開啟Android瀏覽器的意圖,則其Activity不會與您的應用程式放在同一任務中。而是要麼為瀏覽器啟動新任務,要麼如果瀏覽器已經在後臺執行任務,則該任務將被置於前臺來處理新意圖。

無論Activity是在新任務中啟動還是在與啟動它的Activity相同的任務中啟動,“返回”按鈕始終會將使用者帶到上一個Activity。但是,如果啟動指定singleTask啟動模式的Activity,則如果後臺任務中存在該Activity的例項,則將整個任務帶到前臺。此時,回退堆疊壓入該任務中的所有Activity。圖4說明了這種情況。

圖4.表示如何將具有啟動模式“singleTask”的Activity新增到後臺堆疊。如果Activity已經是具有自己的後臺堆疊的後臺任務的一部分,那麼整個後臺堆疊放置在當前任務頂端。

有關在清單檔案中使用啟動模式的詳細資訊,請參閱<activity>元素文件,其中詳細討論了launchMode屬性和可用的值。

注意:您使用launchMode屬性為Activity指定的行為可以被包含在啟動Activity的intent中的標記覆蓋,如下一節中所述。

使用意圖flag

啟動Activity時,您可以通過在傳遞給startActivity()的包含flag的意圖來修改Activity與其任務的預設關聯方式。您可以用來修改預設行為的flag是:

在新任務中啟動Activity。如果您正在啟動的Activity已在任務中執行,則該任務將返回到前臺,並恢復其上一個狀態,並且Activity將在onNewIntent()中接收新的意圖。

這會產生與上一節中討論的“singleTask”launchMode值相同的行為。

如果正在啟動的Activity是當前Activity(在回退堆疊的頂部),則現有例項將接收對onNewIntent()的呼叫,而不是建立Activity的新例項。

這會產生與上一節中討論的“singleTop”launchMode值相同的行為。

如果正在啟動的Activity已在當前任務中執行,則不會啟動該Activity的新例項,而是銷燬其上的所有其他Activity,並將此意圖通過onNewIntent()傳遞給Activity的resume例項(在回退堆疊的頂部))。 生成此行為的launchMode屬性沒有任何值。

FLAG_ACTIVITY_CLEAR_TOP通常與FLAG_ACTIVITY_NEW_TASK結合使用。When used together, these flags are a way of locating an existing activity in another task and putting it in a position where it can respond to the intent.

注意:對於,如果指定Activity的啟動模式是“standard”,它也會從堆疊中刪除,並在其位置啟動新例項以處理傳入的意圖。這是因為當啟動模式為“standard”時,總是為新意圖建立新例項。

關聯性處理

關聯性指Activity希望屬於哪個任務。預設情況下,同一應用程式中的所有Activity都具有彼此的關聯。因此,預設情況下,同一應用程式中的所有Activity都希望處於同一任務中。但是,您可以修改Activity的預設關聯。在不同應用程式中定義的Activity可以共享關聯性,或者可以為同一應用程式中定義的Activity分配不同的任務關聯性。

您可以使用<activity>元素的taskAffinity屬性修改任何給定Activity的關聯性。

taskAffinity屬性採用字串值,該值必須與<manifest>元素中宣告的預設包名稱唯一。因為系統使用該名稱來標識應用程式的預設任務關聯。

關聯性在兩種情況下起作用:

  • 當啟動Activity的intent包含FLAG_ACTIVITY_NEW_TASK標誌時。

預設情況下,新Activity將啟動到呼叫startActivity()的Activity的任務中。它被調到與呼叫者相同的回退棧。但是,如果傳遞給startActivity()的intent包含FLAG_ACTIVITY_NEW_TASK標誌,則系統會查詢另一個任務以容納新Activity。通常,這是一項新任務。但是,它不一定是。如果已存在與新Activity具有相同關聯性的現有任務,則會將活動啟動到該任務中。如果沒有,它開始一項新任務。

如果此標誌導致Activity開始新任務,並且使用者按下主頁按鈕以離開它,則必須有某種方式讓使用者導航回任務。某些實體(例如通知管理器)總是在外部任務中啟動Activity,從不作為自己的一部分,因此它們總是將FLAG_ACTIVITY_NEW_TASK置於它們傳遞給startActivity()的意圖中。如果您有可以由可能使用此標誌的外部實體呼叫的Activity,請注意使用者有一種獨立的方式返回已啟動的任務,例如使用啟動器圖示(任務的根Activity)有一個CATEGORY_LAUNCHER意圖過濾器;請參閱下面的“啟動任務”部分。

  • 當一個活動的allowTaskReparenting屬性設定為“true”時。

在這種情況下,當該任務到達前臺時,Activity可以從它開始的任務移動到它具有親和力的任務。 例如,假設在所選城市中報告天氣狀況的Activity被定義為旅行應用程式的一部分。它與同一應用程式中的其他Activity(預設應用程式關聯)具有相同的關聯,並允許使用此屬性重新生成父項。當您的某個Activity啟動天氣報告者Activity時,它最初屬於與您的Activity相同的任務。但是,當旅行應用程式的任務到達前臺時,天氣報告者Activity將重新分配給該任務並顯示在其中。

提示:如果.apk檔案從使用者的角度包含多個“應用程式”,您可能希望使用taskAffinity屬性為與每個“應用程式”關聯的活動分配不同的關聯。

清理回退棧

如果使用者長時間離開任務,系統將清除任務中除根活動之外的所有Activity。當用戶再次返回任務時,僅還原根活動。系統以這種方式執行,因為在很長一段時間之後,使用者可能已經放棄了之前正在做的事情並返回任務以開始新的事情。

您可以使用一些Activity屬性來修改此行為:

如果在任務的根Activity中將此屬性設定為“true”,則不會發生剛才描述的預設行為。即使經過很長一段時間,任務仍會保留堆疊中的所有Activity。

如果在任務的根Activity中將此屬性設定為“true”,則只要使用者離開任務並返回到該任務,就會將堆疊清除為根Activity。換句話說,它與alwaysRetainTaskState相反。即使在離開任務片刻之後,使用者也始終以初始狀態返回任務。

此屬性類似於clearTaskOnLaunch,但它在單個Activity上執行,而不是整個任務。它還可以導致任何Activity消失,包括根Activity。當它設定為“true”時,Activity仍然是當前會話的任務的一部分。如果使用者離開然後返回任務,它將不再存在。

啟動任務

您可以將活動設定為Activity的入口點,方法是為其指定一個過濾器,其中“android.intent.action.MAIN”作為指定的action,“android.intent.category.LAUNCHER”作為指定的類別。例如:

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

這種意圖過濾器會導致Activity的圖示和標籤顯示在應用程式啟動器中,從而為使用者提供啟動Activity的方法,並在啟動後隨時返回建立的任務。 

第二種能力很重要:使用者必須能夠離開任務,然後使用此Activity啟動器返回該任務。因此,僅當Activity具有ACTION_MAIN和CATEGORY_LAUNCHER過濾器時,才應使用將Activity標記為始終啟動任務的兩種啟動模式“singleTask”和“singleInstance”。例如,想象一下,如果缺少過濾器會發生什麼:intent會啟動“singleTask”Activity,啟動新任務,並且使用者會花一些時間在該任務中工作。然後使用者按下主頁按鈕。該任務現在傳送到後臺並且不可見。現在使用者無法返回任務,因為它未在應用程式啟動器中顯示。

對於您不希望使用者能夠返回Activity的情況,請設定<activity> 元素的finishOnTaskLaunch為“true”(請參閱​​清除堆疊)。

有關如何在概覽螢幕中表示和管理任務和活動的更多資訊,請參閱概述螢幕。