### Google官網文章任務和返回棧《摘抄》
一個 Activity 甚至可以啟動設備上其他應用中存在的 Activity,例如,如果應用想要發送電子郵件,則可將 Intent 定義為執行“發送”操作并加入一些數據,如電子郵件地址和電子郵件。 然后,系統將打開其他應用中聲明自己處理此類 Intent 的 Activity。在這種情況下,Intent 是要發送電子郵件,因此將啟動電子郵件應用的“撰寫”Activity(如果多個 Activity 支持相同 Intent,則系統會讓用戶選擇要使用的 Activity)。發送電子郵件時,Activity 將恢復,看起來好像電子郵件 Activity 是您的應用的一部分。 **即使這兩個 Activity 可能來自不同的應用,但是 Android 仍會將 Activity 保留在相同的任務中,以維護這種無縫的用戶體驗。**
**任務是指在執行特定作業時與用戶交互的一系列 Activity。 這些 Activity 按照各自的打開順序排列在堆棧(即返回棧)中。**
>[info] **注意**: 可以在Activity的onCreate()方法中調用getTaskId()方法(該方法內部調用了類ActivityManagerService的getTaskForActivity方法,而getTaskForActivity這個方法內部又調用了ActivityRecord.getTaskForActivityLocked()方法)得出該Activity所在的棧的ID
**當前 Activity(標記為Activity①)** 啟動**另一個 Activity(標記為Activity②)** 時,該新 Activity(**Activity②**) 會被推送到堆棧頂部,成為焦點所在。 前一個 Activity(**Activity①**) **仍保留在堆棧中,但是處于停止狀態**。**Activity(就是指Activity,不管是Activity①還是Activity②) 停止時,系統會保持其用戶界面的當前狀態**。 用戶按“返回”按鈕時,當前 Activity(**Activity②**) 會從堆棧頂部彈出(Activity(**Activity②**) 被銷毀),而前一個 Activity(**Activity①**) 恢復執行(恢復其 UI 的前一狀態)。
>[info]**總結**: 堆棧中的 Activity 永遠不會重新排列,僅推入和彈出堆棧:由當前 Activity 啟動時推入堆棧;用戶使用“返回”按鈕退出時彈出堆棧。 因此,返回棧以“后進先出”對象結構運行。

圖 1 通過時間線顯示 Activity 之間的進度以及每個時間點的當前返回棧,直觀呈現了這種行為。
圖解:棧stack中原先只有Activity 1 ,之后Activity 1啟動Activity 2,棧stack中有Activity 1 和Activity 2,Activity 1 處于停止狀態,Activity 2處于活動狀態;后來Activity 2又啟動了Activity 3,那Activity 3處于活動狀態,Activity 2 和Activity 1處于停止狀態; 之后按下導航的返回鍵或者調用了finish()方法后,處于棧頂的Activity 3被彈出,Activity 2又處于棧頂,處于活動狀態。這也就印證了**棧的后進先出**的思想 。如果用戶繼續按“返回”,堆棧中的相應 Activity 就會被彈出,以顯示前一個 Activity,直到用戶返回主屏幕為止(或者,返回任務開始時正在運行的任意 Activity)。 當所有 Activity 均從堆棧中移除后,任務即不復存在。任務棧是一種“后進先出”的棧結構,這個比較好理解,每按一下back 鍵就會有一個Activity 出棧,直到棧空為止,當棧中無任何Activity的時候,系統就會回收這個任務棧。
### Android 多任務運行機制

圖 2. 兩個任務:任務 B 在前臺接收用戶交互,而任務 A 則在后臺等待恢復。
任務(Task1)是一個有機整體,當用戶開始新任務(Task2)或通過“主頁”按鈕(Home鍵)轉到主屏幕時,可以將該任務(Task1)移動到“后臺”。 盡管在后臺時,該任務(Task1)中的所有 Activity 全部停止,但是任務(Task1)的返回棧仍舊不變,也就是說,**當另一個任務(Task2)發生時,該任務(Task1)僅僅失去焦點而已**,如圖 2 中所示。然后,任務(Task1)可以返回到“前臺”,用戶就能夠回到離開時的狀態。
例如,假設當前任務(任務 A)的堆棧中有三個 Activity,即當前 Activity 下方還有兩個 Activity。 用戶先按“主頁”按鈕(Home鍵),然后從應用啟動器啟動新應用。 顯示主屏幕時,任務 A 進入后臺。**新應用啟動時,系統會使用自己的 Activity 堆棧為該應用啟動一個任務(任務 B)**。與該應用交互之后,用戶再次返回主屏幕并選擇最初啟動任務 A 的應用。現在,任務 A 出現在前臺,其堆棧中的所有三個 Activity 保持不變,而位于堆棧頂部的 Activity 則會恢復執行。 此時,用戶還可以通過轉到主屏幕并選擇啟動該任務的應用圖標(或者,通過從概覽屏幕選擇該應用的任務)切換回任務 B。這是 Android 系統中的一個多任務示例。
>[warning] **注意**:**后臺可以同時運行多個任務。但是,如果用戶同時運行多個后臺任務,則系統可能會開始銷毀后臺 Activity,以回收內存資源,從而導致 Activity 狀態丟失**。請參閱下面有關 [Activity 狀態](https://developer.android.com/guide/components/tasks-and-back-stack.html#ActivityState)的部分。
Activity 和任務的默認行為總結如下:
1. 當 Activity A 啟動 Activity B 時,Activity A 將會停止,但系統會保留其狀態(例如,滾動位置和已輸入表單中的文本)。如果用戶在處于 Activity B 時按“返回”按鈕,則 Activity A 將恢復其狀態,繼續執行。
2. 用戶通過按“主頁”按鈕(Home鍵)離開任務時,當前 Activity 將停止且其任務會進入后臺。 系統將保留任務中每個 Activity 的狀態。如果用戶稍后通過選擇開始任務的啟動器圖標來恢復任務,則任務將出現在前臺并恢復執行堆棧頂部的 Activity。
3. 如果用戶按“返回”按鈕,則當前 Activity(處于棧頂的那個Activity即上面1中的ActivityB) 會從堆棧彈出并被銷毀。 堆棧中的前一個 Activity(Activity A) 恢復執行。銷毀 Activity 時,系統不會保留該 Activity 的狀態。
4. 即使來自其他任務,Activity 也可以多次實例化。(但是多次實例化,會造成內存資源浪費,具體可參考下面)
#### 一個 Activity 將多次實例化

圖 3. 一個 Activity 將多次實例化。
由于**返回棧中的 Activity 永遠不會重新排列**,因此如果應用允許用戶從多個 Activity 中啟動特定 Activity,則會創建該 Activity 的新實例并推入堆棧中(而不是將 Activity 的任一先前實例置于頂部)。 因此,**應用中的一個 Activity 可能會多次實例化(即使 Activity 來自不同的任務)**,如圖 3 所示。因此,如果用戶使用“返回”按鈕向后導航,則會按 Activity 每個實例的打開順序顯示這些實例(每個實例的 UI 狀態各不相同)。 但是,如果您不希望 Activity 多次實例化,則可修改此行為。 具體操作方法將在后面的管理任務部分中討論。
### 保存 Activity 狀態
正如上文所述,當 Activity 停止時,系統的默認行為會保留其狀態。 這樣一來,當用戶導航回到上一個 Activity 時,其用戶界面與用戶離開時一樣。**但是,在 Activity 被銷毀且必須重建時,您可以而且應當主動使用回調方法保留 Activity 的狀態。**
系統停止您的一個 Activity 時(例如,新 Activity 啟動或任務轉到前臺(即重新開始一個新的任務)),**如果系統需要回收系統內存資源,則可能會完全銷毀該 Activity。 發生這種情況時,有關該 Activity 狀態的信息將會丟失。如果發生這種情況,系統仍會知道該 Activity 存在于返回棧中,但是當該 Activity 被置于堆棧頂部時,系統一定會重建 Activity(而不是恢復 Activity)**。 **為了避免用戶的工作丟失,您應主動通過在 Activity 中實現 [onSaveInstanceState()](https://developer.android.com/reference/android/app/Activity.html#onSaveInstanceState(android.os.Bundle)) 回調方法來保留工作**。
如需了解有關如何保存 Activity 狀態的詳細信息,請參閱 [Activity](https://developer.android.com/guide/components/activities.html#SavingActivityState) 文檔。
### 管理任務
>[info] **情景**: Android 管理任務和返回棧的方式(如上所述,即:將所有連續啟動的 Activity 放入同一任務和“后進先出”堆棧中)非常適用于大多數應用,而您不必擔心 Activity 如何與任務關聯或者如何存在于返回棧中(也可以自定義設置)。
>但是,您可能會決定要中斷正常行為。
>也許您希望應用中的 Activity 在啟動時開始新任務(而不是放置在當前任務中);
>或者,當啟動 Activity 時,您希望將其現有實例上移一層(而不是在返回棧的頂部創建新實例);
>或者,您希望在用戶離開任務時,清除返回棧中除根 Activity 以外的所有其他 Activity。
通過使用 `<activity>` 清單文件元素中的屬性和傳遞給 [startActivity()](https://developer.android.com/reference/android/app/Activity.html#startActivity(android.content.Intent)) 的 Intent 中的標志,您可以執行所有這些操作以及其他操作。
在這一方面,您可以使用的主要 `<activity>` 屬性包括:
[taskAffinity](https://developer.android.com/guide/topics/manifest/activity-element.html#aff)
[launchMode](https://developer.android.com/guide/topics/manifest/activity-element.html#lmode)
[allowTaskReparenting](https://developer.android.com/guide/topics/manifest/activity-element.html#reparent)
[clearTaskOnLaunch](https://developer.android.com/guide/topics/manifest/activity-element.html#clear)
[alwaysRetainTaskState](https://developer.android.com/guide/topics/manifest/activity-element.html#always)
[finishOnTaskLaunch](https://developer.android.com/guide/topics/manifest/activity-element.html#finish)
您可以使用的主要 Intent 標志包括:
[FLAG_ACTIVITY_NEW_TASK](https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_TASK)
[FLAG_ACTIVITY_CLEAR_TOP](https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_CLEAR_TOP)
[FLAG_ACTIVITY_SINGLE_TOP](https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_SINGLE_TOP)
在下文中,您將了解如何使用這些清單文件屬性和 Intent 標志定義 Activity 與任務的關聯方式,以及 Activity 在返回棧中的行為方式。
此外,我們還單獨介紹了有關如何在概覽屏幕中顯示和管理任務與 Activity 的注意事項。 如需了解詳細信息,請參閱[概覽屏幕](https://developer.android.com/guide/components/recents.html)。 通常,您應該允許系統定義任務和 Activity 在概覽屏幕中的顯示方法,并且無需修改此行為。
>[warning] **注意**:大多數應用都不得中斷 Activity 和任務的默認行為: 如果確定您的 Activity 必須修改默認行為,當使用“返回”按鈕從其他 Activity 和任務導航回到該 Activity 時,請務必要謹慎并確保在啟動期間測試該 Activity 的可用性。請確保測試導航行為是否有可能與用戶的預期行為沖突。
### 定義啟動模式
啟動模式允許您定義 Activity 的新實例如何與當前任務關聯。 您可以通過兩種方法定義不同的啟動模式:
1. [使用清單文件](https://developer.android.com/guide/components/tasks-and-back-stack.html#ManifestForTasks)
在清單文件中聲明 Activity 時,您可以指定 Activity 在啟動時應該如何與任務關聯。
2. [使用 Intent 標志](https://developer.android.com/guide/components/tasks-and-back-stack.html#IntentFlagsForTasks)
調用 [startActivity()](https://developer.android.com/reference/android/app/Activity.html#startActivity(android.content.Intent)) 時,可以在 [Inten](https://developer.android.com/reference/android/content/Intent.html)t 中加入一個標志(上面管理任務章節中的3種Intent的flag),用于聲明新 Activity 如何(或是否)與當前任務關聯。
>[info] **注**:某些適用于清單文件的啟動模式不可用作 Intent 標志,同樣,某些可用作 Intent 標志的啟動模式無法在清單文件中定義。
因此,如果 Activity A 啟動 Activity B,則 Activity B 可以在其清單文件中定義它應該如何與當前任務關聯(如果可能),并且 Activity A 還可以請求 Activity B 應該如何與當前任務關聯。如果這兩個 Activity(Activity A和Activity B) 均定義 Activity B 應該如何與任務關聯(可以一個在intent中,一個在清單文件中),則 Activity A 的請求(如 Intent 中所定義)優先級要高于 Activity B 的請求(如其清單文件中所定義)。
#### **使用清單文件(AndroidMainifest啟動模式)**
在清單文件中聲明 Activity 時,您可以使用 `<activity>` 元素的 [launchMode](https://developer.android.com/guide/topics/manifest/activity-element.html#lmode) 屬性指定 Activity 應該如何與任務關聯。
[launchMode](https://developer.android.com/guide/topics/manifest/activity-element.html#lmode) 屬性指定有關應如何將 Activity 啟動到任務中的指令。您可以分配給 launchMode 屬性的啟動模式共有四種:
* **"standard"(默認模式)**
默認。系統在啟動 Activity 的任務中創建 Activity 的新實例并向其傳送 Intent。Activity 可以多次實例化,而每個實例均可屬于不同的任務,并且一個任務可以擁有多個實例。如果不指定Activity 的啟動模式,則使用這種方式啟動Activity。這種啟動模式每次都會創建新的實例,每次點擊standard 模式創建Activity 后,都會創建新的MainActivity 覆蓋在原Activity 上。

被創建的實例的生命周期符合典型情況下Activity 的生命周期,如上節描述,它的onCreate 、onStart 、onResume 都會被調用;在這種模式下,誰啟動了這個Activity,那么這個Activity 就運行在啟動它的那個Activity所在的棧中。
>[warning] **注意**:當我們用ApplicationContext 去啟動standard 模式的Activity 的時候會報錯,錯誤如下:
~~~
E/AndroidRuntime(674): android.util.AndroidRuntimeException: Calling startActivity
from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.
Is this really what you want?
~~~
相信這句話讀者一定不陌生,這是因為standard 模式的Activity 默認會進入啟動它的Activity 所屬的任務找中,但是由于非Activity 類型的Context 〈如ApplicationContext )并沒有所謂的任務棧,所以這就有問題了。解決這個問題的方法是為待啟動Activity 指定FLAG_ACTIVlTY _NEW TASK 標記位,這樣啟動的時候就會為它創建一個新的任務棧,這個時候待啟動Activity 實際上是以singleTask 模式啟動的,讀者可以仔細體會。
* * * * *
* **"singleTop"(棧頂復用模式)**
如果當前任務的頂部已存在 Activity 的一個實例,則系統會通過調用該實例的 [onNewIntent()](https://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)) 方法向其傳送 Intent,通過此方法的參數我們可以取出當前請求的信息;而不是創建 Activity 的新實例。需要注意的是,這個Activity 的onCreate 、onStart 不會被系統調用,因為它井沒有發生改變。
Activity 可以多次實例化,而每個實例均可屬于不同的任務,并且一個任務可以擁有多個實例;這句話是正確的,但前提是**位于返回棧頂部的 Activity 并不是 Activity 的現有實例**,如果是Activity 的現有實例,而且啟動模式是**singleTop**,則不會多次實例化。
例如,假設任務的返回棧包含根 Activity A 以及 Activity B、C 和位于頂部的 D(堆棧是 A-B-C-D;D 位于頂部)。收到針對 D 類 Activity 的 Intent。如果 D 具有默認的 "standard" 啟動模式,則會啟動該類的新實例,且堆棧會變成 A-B-C-D-D。但是,如果 D 的啟動模式是 "singleTop",則 D 的現有實例會通過 [onNewIntent()](https://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)) 接收 Intent,因為它位于堆棧的頂部;而堆棧仍為 A-B-C-D。但是,**如果收到針對 B 類 Activity 的 Intent,則會向堆棧添加 B 的新實例,即便其啟動模式為 "singleTop" 也是如此**,也就是說,**如果B沒有位于棧頂,即便B的啟動模式是"singleTop",在收到針對 B 類 Activity 的 Intent時,也會向堆棧添加 B 的新實例**。
>[info] **注**:為某個 Activity 創建新實例時,用戶可以按“返回”按鈕返回到前一個 Activity。 但是,當 Activity 的現有實例處理新 Intent 時,則在新 Intent 到達 [onNewIntent()](https://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)) 之前,用戶無法按“返回”按鈕返回到 Activity 的狀態。
總結:這種模式通常適用于就收到消息后現實的界面,比如QQ接收到消息后彈出Activity,如果一次來了10條消息,總不能一次彈10個Activity。
* * * * *
* **"singleTask"(棧內復用模式)**
系統創建新任務并實例化位于新任務底部的 Activity。但是,如果該 Activity 的一個實例已存在于一個單獨的任務中,則系統會通過調用現有實例的 onNewIntent() 方法向其傳送 Intent,而不是創建新實例。一次只能存在 Activity 的一個實例。
>[info]**注**:盡管 Activity 在新任務中啟動,但是用戶按“返回”按鈕仍會返回到前一個 Activity。
singleTask 模式與singlTop 模式類似,只不過singleTop 是檢測檢頂元素是否是需要啟動的Activity ,而singleTask 是檢測整個Activity 戰中是否存在當前需要啟動的Activity。如果存在, 則將該Activity 置于棧頂,并將該Activity 以上的Activity 都銷毀(由于singleTask 默認具有clearTop 的效果)。不過這里是指在同一個App 中啟動這個singleTask 的Activity,如果是其他程序以singleTask 模式來啟動這個Activity ,那么它將創建一個新的任務棧。不過這里有一點需要注意的是,如果啟動的模式為singleTask 的Activity 巳經在后臺一個任務棧中了,那么啟動后,而后臺的這個任務棧將一起被切換到到前臺。
舉例如下:
1. 比如目前任務棧S1 中的情況為ABC,這個時候Activity D 以singleTask 模式請求啟動,其所需要的任務棧為S2 ,由于S2 和D 的實例均不存在,所以系統會先創建任務棧S2 ,然后再創建D 的實例井將其入棧到S2 。
2. 另外一種情況,假設D 所需的任務棧為S1,其他情況如上面例子l 所示,那么由于S1 已經存在,所以系統會直接創建D的實例井將其入棧到S1。
3. 如果D 所需的任務棧為S1,并且當前任務棧S1 的情況為ADBC ,根據棧內復用的原則,此時D 不會重新創建,系統會把D 切換到棧頂并調用其onNewlntent 方法,同時由于singleTask 默認具有clearTop 的效果,會導致棧內所有在D 上面的Activity全部出棧,于是最終S1中的情況為AD。這一點比較特殊。
上面例子中所需要的任務棧,具體是指什么??可以下文中摘抄的Android開發藝術探討中內容
**總結**:使用這個模式創建的Activity不是在新的任務棧中被打開, 就是將已經打開的Activity切換到前臺,所以這種啟動模式通常可以用來退出整個應用: 將主Activity 設為singleTask 模式,然后在要退出的Activity 中轉到主Activity,從而將主Activity之上的Activity 都清除,然后重寫主Activity 的onNewIntent()方法,在方法中加上一行代碼finish(),將最后一個Activity 結束掉。
* * * * *
* **"singleInstance(單實例模式)"**
與 "singleTask" 相同,只是系統不會將任何其他 Activity 啟動到包含實例的任務中。該 Activity 始終是其任務唯一僅有的成員;由此 Activity 啟動的任何 Activity 均在單獨的任務中打開。而且**代碼中不能實現相同的啟動模式效果。**
這是一種加強的singleTask模式,它除了具有singleTask模式的所有特性外,還加強了一點,那就是具有此種模式的Activity 只能單獨地位于一個任務棧中,換句話說,比如Activity A 是singlelnstance 模式, 當A 啟動后,系統會為它創建一個新的任務棧,然后A 獨自在這個新的任務找中,由于棧內復用的特性,后續的請求均不會創建新的Activity,除非這個獨特的任務棧被系統銷毀了。
singleInstance 這種啟動模式和使用的瀏覽器工作原理類似。在多個程序中訪問瀏覽器時 ,如果當前瀏覽器沒有打開,則打開瀏覽器,否則會在當前打開的瀏覽器中訪問。申明為singleInstance 的Activity 會出現在一個新的任務棧中,而該任務棧中只存在這一個Activity 。舉個例子米說,如果應用A 的任務棧中創建了MainActivity 實例,且啟動模式為singleInstance,如果應用B 也要激活MainActivity , 則不需要創建,兩個應用共享該Activity實例。這種啟動模式常用于需要與程序分離的界面,如在SetupWizard中調用緊急呼叫,就是使用這種啟動模式。
不同于以上3 種啟動模式,指定為singlelnstance 模式的活動會啟用一個新的返回找來管理這個活動(其實如果singleTask 模式指定了不同的taskAftinity ,也會啟動一個新的返回棧)。那么這樣做有什么意義呢?
想象以下場景,假設我們的程序中有一個活動是允許其他程序調用的,如果我們想實現其他程序和我們的程序可以共享這個活動的實例,應該如何實現呢?
使用前面3 種啟動模式肯定是做不到的,因為每個應用程序都會有自己的返回棧,同一個活動在不同的返回棧中入棧時必然是創建了新的實例。而使用singlelnstance 模式就可以解決這個問題,在這種模式下會有一個單獨的返回棧來管理這個活動,不管是哪個應用程序來訪問這個活動,都共用的同一個返回棧,也就解決了共享活動實例的問題。(具體事例可參考郭霖的第一行代碼中例子)
* * * * *
#### 總結及補充
上面介紹了幾種啟動模式,這里需要補充一種情況,我們假設目前有2 個任務棧,前臺任務棧的情況為AB ,而后臺任務棧的情況為CD ,這里假設CD 的啟動模式均為singleTask 。現在請求啟動D,那么整個后臺任務校都會被切換到前臺,這個時候整個后臺任務棧變成了ABCD。當用戶按back 鍵的時候,列表中的Activity 會一一出錢,如圖1-7 所示。如果不是請求啟動D 而是啟動C,那么情況就不一樣了,請看圖1-8 ,具體原因在本節后面會再進行詳細分析。


* **什么是某個Activity所需要的任務棧**?
在singleTask 啟動模式中,多次提到某個Activity所需要的任務棧,什么是Activity 所需要的任務找呢?這要從一個參數說起: **TaskAffinity**,可以翻譯為**任務相關性,可以定義Activity 的親和關系**。
- TaskAffinity,這個參數在清單文件的`<activity>`標簽的的`android:taskAffinity="string"`,同時特也是類ActivityInfo的成員變量;它**標識了一個Activity 所需要的任務棧的名字**,**默認情況下,所有Activity所需的任務棧的名字為應用的包名**。當然,我們**可以為每個Activity 都單獨指定TaskAffinity屬性**,這個**屬性值必須不能和包名相同,否則就相當于沒有指定**。
- **TaskAffinity 屬性主要和singleTask 啟動模式或者allowTaskReparenting 屬性配對使用,在其他情況下沒有意義**。
- 另外,**任務棧分為前臺任務棧和后臺任務棧,后臺任務棧中的Activity 位于暫停狀態,用戶可以通過切換將后臺任務棧再次調到前臺。**
- 當TaskAffinity 和singleTask 啟動模式配對使用的時候,它是具有該模式的Activity 的目前任務棧的名字,待啟動的Activity 會運行在名字和TaskAffinity 相同的任務棧中。
- 當TaskAffinity 和allowTaskReparenting 結合的時候,這種情況比較復雜,會產生特殊的效果。當一個應用A 啟動了應用B 的某個Activity 后,如果這個Activity 的allowTaskReparenting 屬性為true 的話,那么當應用B 被啟動后,此Activity 會直接從應用A 的任務棧轉移到應用B 的任務棧中。這還是很抽象,可看下面的例子以及解釋。
- **allowTaskReparenting**這個參數可以在`<application>`和`<activity>`標簽定義,具體含義是:當啟動 Activity 的任務接下來轉至前臺時,Activity 是否能從該任務轉移至與其有親和關系的任務 —“true”表示它可以轉移,“false”表示它仍須留在啟動它的任務處。如果未設置該屬性,則對 Activity 應用由 `<application>` 元素的相應 allowTaskReparenting 屬性設置的值。 **默認值**為“**false**”。
- **正常情況下,當 Activity 啟動時,會與啟動它的任務關聯,并在其整個生命周期中一直留在該任務處**。您可以利用該屬性強制 Activity 在其當前任務不再顯示時將其父項更改為與其有親和關系的任務。**該屬性通常用于使應用的 Activity 轉移至與該應用關聯的主任務**。
- **Activity 的親和關系由 taskAffinity 屬性定義**。 任務的親和關系通過讀取其根 Activity 的親和關系來確定。因此,按照定義,根 Activity 始終位于具有相同親和關系的任務之中。 由于**具有“singleTask”或“singleInstance”啟動模式的 Activity 只能位于任務的根,因此更改父項僅限于“standard”和“singleTop”模式。**
- **舉例如下**:再具體點,比如現在有2 個應用A和B, A 啟動了B 的一個Activity C,然后按Home 鍵回到桌面,然后再單擊B 的桌面圖標,這個時候井不是啟動了B 的主Activity ,而是重新顯示了已經被應用A 啟動的Activity C,或者說, C 從A 的任務棧轉移到了B 的任務棧中。可以這么理解,由于A 啟動了C,這個時候C 只能運行在A 的任務棧中,但是C 屬于B 應用,正常情況下,它的Task.Affinity 值肯定不可能和A 的任務棧相同〈因為包名不同〉。所以,當B 被啟動后, B 會創建自己的任務棧,這個時候系統發現C 原本所想要的任務棧已經被創建了,所以就把C 從A 的任務棧中轉移過來了。
無論 Activity 是在新任務中啟動,還是在與啟動 Activity 相同的任務中啟動,用戶按“返回”按鈕始終會轉到前一個 Activity。 但是,如果啟動指定 singleTask 啟動模式的 Activity,則當某后臺任務中存在該 Activity 的實例時,整個任務都會轉移到前臺。此時,返回棧包括上移到堆棧頂部的任務中的所有 Activity。 圖 4 顯示了這種情況。

圖 4. 顯示如何將啟動模式為“singleTask”的 Activity 添加到返回棧。 如果 Activity 已經是某個擁有自己的返回棧的后臺任務的一部分,則整個返回棧也會上移到當前任務的頂部。
如需了解有關在清單文件中使用啟動模式的詳細信息,請參閱 `<activity> `元素文檔,其中更詳細地討論了 launchMode 屬性和可接受的值。
>[info] **注**:使用 launchMode 屬性為 Activity 指定的行為可由 Intent 附帶的 Activity 啟動標志替代,下文將對此進行討論。
#### **使用 Intent 標志**
> 啟動 Activity 時,您可以通過在傳遞給 startActivity() 的 Intent 中加入相應的標志,修改 Activity 與其任務的默認關聯方式。
代碼如下:
~~~
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent . addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
~~~
Activity 的Flags 有很多,這里主要分析一些比較常用的標記位。標記位的作用很多,
- 設定Activity 的啟動模式,比如FLAG_ACTIVITY_NEW_TASK 和FLAG_ACTIVITY _SINGLE_ TOP 等:
- 影響Activity 的運行狀態,比如FLAG_ACTIVITY_CLEAR_TOP 和FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 等。
下面主要介紹幾個比較常用的標記位,剩下的標記位讀者可以查看官方文檔去了解,**大部分情況下,我們不需要為Activity 指定標記位**,因此,對于標記位理解即可。在使用標記位的時候,要注意有些標記位是系統內部使用的,應用程序不需要去手動設置這些標記位以防出現問題。
- **FLAG_ACTIVITY_NEW_TASK**
在新任務中啟動 Activity。如果已為正在啟動的 Activity 運行任務,則該任務會轉到前臺并恢復其最后狀態,同時 Activity 會在 onNewIntent() 中收到新 Intent。
正如前文所述,這**會產生與 "singleTask"launchMode 值相同的行為**。
使用一個新的Task 來啟動一個Activity ,但啟動的每個Activity 都將在一個新的Task 中。該Flag 通常使用在從Service 中啟動Activity 的場景,由于在Service 中并不存在Activity 棧,所以使用該Flag來創建一個新的Activity 棧,井創建新的Activity實例。
- **FLAG_ACTIVITY_SINGLE_TOP**
如果正在啟動的 Activity 是當前 Activity(位于返回棧的頂部),則現有實例會接收對 onNewIntent() 的調用,而不是創建Activity 的新實例。
正如前文所述,這**會產生與 "singleTop"launchMode 值相同的行為**。
- **FLAG_ACTIVITY_CLEAR_TOP**
如果正在啟動的 Activity 已在當前任務中運行,則會銷毀當前任務頂部的所有 Activity,并通過 onNewIntent() 將此 Intent 傳遞給 Activity 已恢復的實例(現在位于頂部),而不是啟動該 Activity 的新實例。
產生這種行為的 launchMode 屬性沒有值。
具有此標記為的Activity,當它啟動時,在同一個任務棧中所有位于它上面的Activity 都要出棧。這個模式一般需要和FLAG_ACTIVITY_ NEW_ TASK 配合使用,在這種情況下, 被啟動Activity 的實例如果已經存在,那么系統就會調用它的onNewIntent()。 如果被啟動的Activity 采用standard 模式啟動,那么它連同它之上的Activity 都要出棧,系統會創建新的Activity 實例并放入棧頂。通過1.2.1 節中的分析可以知道, singleTask啟動模式默認就具有此標記位的效果。
FLAG_ACTIVITY_CLEAR_TOP 通常與 FLAG_ACTIVITY_NEW_TASK 結合使用。一起使用時,通過這些標志,可以找到其他任務中的現有 Activity,并將其放入可從中響應 Intent 的位置。
>[info] **注**:如果指定 Activity 的啟動模式為 "standard",則該 Activity 也會從堆棧中移除,并在其位置啟動一個新實例,以便處理傳入的 Intent。 這是因為當啟動模式為 "standard" 時,將始終為新 Intent 創建新實例。
- **FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS**
具**有這個標記的Activity 不會出現在歷史Activity 的列表**中,當某些情況下我們不希望用戶通過歷史列表回到我們的Activity 的時候這個標記比較有用。它等同于在XML 中指定Activity 的屬性`android :excludeFromRecents=true”`
### 處理關聯
**“關聯”指示 Activity 優先屬于哪個任務**。**默認情況下,同一應用中的所有 Activity 彼此關聯**。 因此,**默認情況下,同一應用中的所有 Activity 優先位于相同任務中**。 不過,您**可以修改 Activity 的默認關聯**:在不同應用中定義的 Activity 可以共享關聯,或者可為在同一應用中定義的 Activity 分配不同的任務關聯。
#### 如何關聯
**可以使用 `<activity>` 元素的 [taskAffinity](https://developer.android.com/guide/topics/manifest/activity-element.html#aff) 屬性修改任何給定 Activity 的關聯。**
**taskAffinity** 屬性**取字符串值**,該值必**須不同于在 [`<manifest> `](https://developer.android.com/guide/topics/manifest/manifest-element.html)元素中聲明的默認軟件包名稱,因為系統使用該名稱標識應用的默認任務關聯。**
在兩種情況下,關聯會起作用:
- 啟動 Activity 的 Intent 包含 [FLAG_ACTIVITY_NEW_TASK](https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_TASK) 標志。
**默認情況下,新 Activity 會啟動到調用 startActivity() 的 Activity 任務中。它將推入與調用方相同的返回棧**。 **但是,如果傳遞給 [startActivity()](https://developer.android.com/reference/android/app/Activity.html#startActivity(android.content.Intent)) 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 標志,則系統會尋找其他任務來儲存新 Activity。這通常是新任務,但未做強制要求**。 **如果現有任務與新 Activity 具有相同關聯,則會將 Activity 啟動到該任務中。 否則,將開始新任務。**
**如果此標志導致 Activity 開始新任務,且用戶按“主頁”按鈕離開,則必須為用戶提供導航回任務的方式**。 有些實體(如通知管理器)始終在外部任務中啟動 Activity,而從不作為其自身的一部分啟動 Activity,因此它們始終將 FLAG_ACTIVITY_NEW_TASK 放入傳遞給 startActivity() 的 Intent 中。請注意,**如果 Activity 能夠由可以使用此標志的外部實體調用,則用戶可以通過獨立方式返回到啟動的任務**,例如,使用啟動器圖標(任務的根 Activity 具有 [CATEGORY_LAUNCHER](https://developer.android.com/reference/android/content/Intent.html#CATEGORY_LAUNCHER) Intent 過濾器;請參閱下面的[啟動任務](https://developer.android.com/guide/components/tasks-and-back-stack.html#Starting)部分)。
- Activity 將其 allowTaskReparenting 屬性設置為 "true"。
**在這種情況下,Activity 可以從其啟動的任務移動到與其具有關聯的任務(如果該任務出現在前臺)**。
例如,假設將報告所選城市天氣狀況的 Activity(**假定為Activity W**) 定義為**旅行應用(App T**)的一部分。 它(**Activity W**)與同一應用(**App T**)中的其他 Activity 具有相同的關聯(默認應用關聯),并允許利用此屬性重定父級。當您的一個 Activity (**Activity O**)啟動天氣預報 Activity(**Activity W**) 時,它(**Activity W**)最初所屬的任務與您的 Activity (**Activity O**)相同。 但是,當旅行應用(**App T**)的任務出現在前臺時,系統會將天氣預報 Activity (**Activity W**)重新分配給該任務并顯示在其中。
>[info] **提示**:如果從用戶的角度來看,一個 .apk 文件包含多個“應用”,則您可能需要使用 [taskAffinity ](https://developer.android.com/guide/topics/manifest/activity-element.html#aff)屬性將不同關聯分配給與每個“應用”相關的 Activity。
### 清理返回棧
**如果用戶長時間離開任務,則系統會清除所有 Activity 的任務,根 Activity 除外。 當用戶再次返回到任務時,僅恢復根 Activity。系統這樣做的原因是,經過很長一段時間后,用戶可能已經放棄之前執行的操作,返回到任務是要開始執行新的操作。**
您可以使用下列幾個 Activity 屬性修改此行為:
- [alwaysRetainTaskState](https://developer.android.com/guide/topics/manifest/activity-element.html#always)
如果在任務的根 Activity 中將此屬性設置為 "true",則不會發生剛才所述的默認行為。即使在很長一段時間后,任務仍將所有 Activity 保留在其堆棧中。
* [clearTaskOnLaunch](https://developer.android.com/guide/topics/manifest/activity-element.html#clear)
**如果在任務的根 Activity 中將此屬性設置為 "true",則每當用戶離開任務然后返回時,系統都會將堆棧清除到只剩下根 Activity。** 換而言之,它與 alwaysRetainTaskState 正好相反。 **即使只離開任務片刻時間,用戶也始終會返回到任務的初始狀態。**
* [finishOnTaskLaunch](https://developer.android.com/guide/topics/manifest/activity-element.html#finish)
此屬性類似于 clearTaskOnLaunch,但它**對單個 Activity 起作用,而非整個任務**。 此外,它還**有可能會導致任何 Activity 停止,包括根 Activity。** **設置為 "true" 時,Activity 仍是任務的一部分,但是僅限于當前會話。如果用戶離開然后返回任務,則任務將不復存在。**
### 啟動任務
**通過為 Activity 提供一個以 "`android.intent.action.MAIN`" 為指定操作、以 "`android.intent.category.LAUNCHER`" 為指定類別的 Intent 過濾器,您可以將 Activity 設置為任務的入口點。** 例如:
~~~
<activity ... >
<intent-filter ... >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>
~~~
此類 Intent 過濾器會使 Activity 的圖標和標簽顯示在應用啟動器中,讓用戶能夠啟動 Activity 并在啟動之后隨時返回到創建的任務中。
**第二個功能**非常重要:**用戶必須能夠在離開任務后,再使用此 Activity 啟動器返回該任務**。 因此,**只有在 Activity 具有 [ACTION_MAIN](https://developer.android.com/reference/android/content/Intent.html#ACTION_MAIN) 和 [CATEGORY_LAUNCHER](https://developer.android.com/reference/android/content/Intent.html#CATEGORY_LAUNCHER) 過濾器時,才應該使用將 Activity 標記為“始終啟動任務”的兩種啟動模式,即 "singleTask" 和 "singleInstance"**。例如,我們可以想像一下如果缺少過濾器會發生什么情況: Intent 啟動一個 "singleTask" Activity,從而啟動一個新任務,并且用戶花了些時間處理該任務。然后,用戶按“主頁”按鈕。 任務現已發送到后臺,而且不可見。現在,用戶無法返回到任務,因為該任務未顯示在應用啟動器中。
**如果您并不想用戶能夠返回到 Activity,對于這些情況,請將 `<activity>`元素的 [finishOnTaskLaunch](https://developer.android.com/guide/topics/manifest/activity-element.html#finish) 設置為 "true"(請參閱清理清理返回棧)。**
有關如何在概覽屏幕中顯示和管理任務與 Activity 的更多信息,請參閱[概覽屏幕](https://developer.android.com/guide/components/recents.html)。
### Android群英傳《摘抄》
- 前言
- Android系統的體系結構
- Dalvik VM 和 JVM 的比較
- Android 打包應用程序并安裝的過程
- Android ADB工具
- Android應用開發
- Android UI相關知識總結
- Android 中window 、view、 Activity的關系
- Android應用界面
- Android中的drawable和bitmap
- AndroidUI組件adapterView及其子類和Adapter的關系
- Android四大組件
- Android 數據存儲
- SharedPreference
- Android應用的資源
- 數組資源
- 使用Drawable資源
- Material Design
- Android 進程和線程
- 進程
- 線程
- Android Application類的介紹
- 意圖(Intent)
- Intent 和 Intent 過濾器(Google官網介紹)
- Android中關于任務棧的總結
- 任務和返回棧(官網譯文)
- 總結
- Android應用安全現狀與解決方案
- Android 安全開發
- HTTPS
- 安卓 代碼混淆與打包
- 動態注入技術(hook技術)
- 一、什么是hook技術
- 二、常用的Hook 工具
- Xposed源碼剖析——概述
- Xposed源碼剖析——app_process作用詳解
- Xposed源碼剖析——Xposed初始化
- Xposed源碼剖析——hook具體實現
- 無需Root也能Hook?——Depoxsed框架演示
- 三、HookAndroid應用
- 四、Hook原生應用程序
- 五、Hook 檢測/修復
- Android 應用的逆向與加固保護技術
- OpenCV在Android中的開發
- Android高級開發進階
- 高級UI
- UI繪制流程及原理
- Android新布局ConstraintLayout約束布局
- 關鍵幀動畫
- 幀動畫共享元素變換
- Android異步消息處理機制完全解析,帶你從源碼的角度徹底理解
- Android中為什么主線程不會因為Looper.loop()里的死循環卡死?
- 為什么 Android 要采用 Binder 作為 IPC 機制?
- JVM 中一個線程的 Java 棧和寄存器中分別放的是什么?
- Android源碼的Binder權限是如何控制?
- 如何詳解 Activity 的生命周期?
- 為什么Android的Handler采用管道而不使用Binder?
- ThreadLocal,你真的懂了嗎?
- Android屏幕刷新機制