Activity的任務棧Task以及啟動模式與Intent的Flag詳解

什么是任務棧(Task)

官方文檔是這么解釋的

任務是指在執行特定作業時與用戶交互的一系列 Activity。 這些 Activity 按照各自的打開順序排列在堆棧(即“返回棧”)中。

其實就是以棧的結構(先進后出)將依次打開的activity記錄.

為什么要用任務棧

為了記錄用戶開啟了那些activity,記錄這些activity開啟的先后順序,google引入任務棧(task stack)概念,幫助維護好的用戶體驗。

如何查看當前系統的任務棧

手機中 --> 長按home或者多任務鍵會進到 概覽屏幕 的一個界面
命令行中 --> adb shell dumpsys activity

概覽屏幕(Overview Screen)

概覽屏幕(也稱為最新動態屏幕、最近任務列表或最近使用的應用)是一個系統級別 UI,其中列出了最近訪問過的Activity和任務。 用戶可以瀏覽該列表并選擇要恢復的任務,也可以通過滑動清除任務將其從列表中刪除。 對于 Android 5.0 版本(API 級別 21),包含多個文檔的同一 Activity 的多個實例可能會以任務的形式顯示在概覽屏幕中。例如,Google Drive 可能對多個 Google 文檔中的每個文檔均執行一個任務。每個文檔均以任務的形式顯示在概覽屏幕中。

Task中activity的特點:

  1. 可以來自不同的app
  2. 可以運行在不同進程

影響Task的activity的屬性和Intent標識

Activity的屬性:

  1. launchMode
  2. taskAffinity
  3. allowTaskReparenting
  4. clearTaskOnLaunch
  5. alwaysRetainTaskState
  6. finishOnTaskLaunch

Intent的標識(四個與task直接關系的):

  1. FLAG_ACTIVITY_NEW_TASK
  2. FLAG_ACTIVITY_CLEAR_TOP
  3. FLAG_ACTIVITY_SINGLE_TOP
  4. FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
    ...

什么是Activity的啟動模式(LaunchMode)

啟動模式簡單地說就是Activity啟動時的策略,在AndroidManifest.xml中的標簽的android:launchMode屬性設置
啟動模式有4種,分別為standard、singleTop、singleTask、singleInstance;

這四種模式影響了Activity所在的任務棧.

使用方式:在清單文件中activity的節點加入launchMode屬性

  1. standard

默認模式,當Intent發送的時候,每次打開都會創建一個新的Activity實例。
如果app1啟動了app2的activity,則會將APP2的activity自動加入到app1的activity所在的task

在5.0中,也沒有出現跨應用會在新的task中啟動activity的的情況 與該文章Understand Android Activity's launchMode: standard, singleTop, singleTask and singleInstance描述的并不太一樣

  1. singleTop

幾乎和standard模式一模一樣,一個singleTop的Activity的實例可以無限多,唯一的區別是如果當前activity已經在棧頂的話,則不會再創建一個新的activity,通過onNewIntent()將intent發送給現有的Activity。

  1. singleTop模式,只在當前任務棧中生效.
  2. 如果通過startActivityForResult啟動一個設置了singleTop的activity,singleTop模式將無效(不知道為什么網上很多人說該設置該singleTop也會導致立即在onActivityResult中返回一個為cancel的resultCode,實測下來4.x,5.x的版本都沒問題)

onNewIntent()使用Tips

1. 方法體中需手動調用setIntent(intent),否則之后的getIntent()獲取的都是舊的intent對象;
2. 被onNewIntent方式打開的activity,對生命周期的影響.
 1. 之前activity是resume狀態,onNewIntent()后只會調用onResume()方法
  2. 否則按照 `onNewIntent->onRestart->onStart->onResume->.`

應用場景

這種啟動模式的用例之一就是搜索功能。假設我們創建了一個搜索框,點擊搜索的時候將導航到一個顯示搜索結果列表的SearchActivity中,為了更好的用戶體驗,這個搜索框一般也會被放到SearchActivity中,這樣用戶想要再次搜索就不需要按返回鍵。
想像一下,如果每次顯示搜索結果的時候我們都啟動一個新的activity,10次搜索10個activity,那樣當我們想返回最初的那個activity的時候需要按10次返回。
所以我們應該這樣,如果棧頂已經有一個SearchActivity,我們將Intent發送給現有的activity,讓它來更新搜索結果。這樣就只會有一個在棧頂的SearchActivity,只需點一次back就可以回到之前的activity。
不管怎樣,singleTop和它的調用者處在一個任務中。如果你想要讓intent發送給另一個任務中處于棧頂的Activity,是不行的。
而當Intent來自于另外一個應用的時候,新的Activity的啟動方式和standard模式是一致的。

  1. singleTask

首先要引出taskAffinity這個activity的屬性.

把TASK比作一個班級,affinity則更像是這個班級的班級名稱,學校比做系統,Activity更像是班級里的學生

*如果沒有對activity設置該屬性的話,默認為application的**taskAffinity**,如果application也沒有設置,則為app的包名.*

啟動一個singleTask模式的activity,會首先在系統中找與它的taskAffinity屬性一致的任務棧,

  1. 先找task

    1. 沒有特別指定taskAffinity,則為當前的task
    2. 如果指定了taskAffinity,先在系統中查找task,如果找不到則創建一個新的task,將activity作為root放置其中.
  2. 啟動Activity

如果第一步中的task中已經有了這個activity的實例,則將其顯示(**將task中該activity上層的activity都pop出任務棧**),同時intent將被通過onNewIntent()發送. 

對設置為singleTask的activity的總結

1. `并不是一定會在新的任務棧中打開.(具體要根據taskAffinity(班級名稱)看系統(學校)中是否已經有這個任務棧(班級)了).`
2. `如果需要在新的任務棧中啟動,就需要為activity設置獨立的taskAffinity.`
3. `如果任務棧中已存在該activity,那么會將上層的所有activity彈出.`
4. `如果當前activity是在新的任務棧中打開的話,那么之后在該activity中通過默認方式啟動的activity都在這個新的任務棧(這個跟我們接下里要講的singleInstance有區別)`
5. `如果是在新的任務棧中啟動的話,最近任務列表(android的多任務鍵按下后)會有兩個,可選擇返回至相應的任務棧`
6. 當作為startActivityForResult啟動的目標時
    1. 4.x版本.會立刻在上個activity中onActivityResult中返回一個為cancel的resultCode.(不管新的activity是否是在新的任務棧中啟動)
    2. 5.x版本.不管是否定義了taskAffinity,都會把將要被啟動的activity的啟動模式忽略,onActivityResult方法會正常回調

應用場景

該模式的使用場景多類似于郵件客戶端的收件箱或者社交應用的時間線Activity(朋友圈)

  1. singleInstance

與 "singleTask" 基本相同,總是該Activity始終是其所在task中唯一僅有的成員;之后在該activity中啟動的activity都不會在其所在的task中.

總結

  1. 當作為startActivityForResult啟動的目標時(下文中的它都是指被啟動的activity)
    1. 4.x版本.在新的任務棧中啟動,并立刻在啟動它的activity中的onActivityResult中返回一個為cancel的resultCode.singleInstance的特點還在
    2. 5.x版本.并不會在新的任務棧中啟動,而是直接在當前任務棧啟動(會出現多個實例),啟動它的activity的onActivityResult方法會在它關閉后,正常回調.重點是被它開啟的activity將運行在另外一個新的任務棧中.

應用場景

  1. 呼叫來電界面 InCallScreen

常用的Intent Flag

  1. FLAG_ACTIVITY_NEW_TASK

> 文檔摘錄: When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in. See FLAG_ACTIVITY_MULTIPLE_TASK for a flag to disable this behavior.`當使用這個flag時,如果task中已經有了你要啟動的activity的話,就不再啟動一個新的activity了,當前**task**會被帶到前臺(不管這個activity是否在前臺,有可能activity上邊還壓有別的activity).如果不想要這種行為,可以用FLAG_ACTIVITY_MULTIPLE_TASK.

> 比如說原來棧中情況是`A,B,C`,在`C`中啟動`D`,如果在Manifest.xml文件中給`D`添加了Affinity的值和`C`所在的Task中的不一樣的話,則會在新標記的Affinity所存在的Task中看是否這個activity已經啟動,如果沒啟動,則直接將activity啟動.如果啟動了,直接將`D`所在的task帶入到前臺;如果是默認的或者指定的Affinity和Task一樣的話,就和標準模式一樣了啟動一個新的Activity.
  1. FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | FLAG_ACTIVITY_NEW_DOCUMENT (API21)

> FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET在API 21的時候,被FLAG_ACTIVITY_NEW_DOCUMENT代替

如果一個Intent中包含此屬性,則它轉向的那個Activity以及在那個Activity其上的所有Activity都會在task重置時被清除出task。當我們將一個后臺的task重新回到前臺時,系統會在特定情況下為這個動作附帶一個FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記,意味著必要時重置task,這時FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET就會生效。經過測試發現,對于一個處于后臺的應用,如果在launcher中點擊應用,這個動作中含有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記,長按Home鍵,然后點擊最近記錄,這個動作不含FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記,所以前者會清除,后者不會.
應用場景:
比如我們在應用主界面要選擇一個圖片,然后我們啟動了圖片瀏覽界面,但是把這個應用從后臺恢復到前臺時,為了避免讓用戶感到困惑,我們希望用戶仍然看到主界面,而不是圖片瀏覽界面,這個時候我們就要在轉到圖片瀏覽界面時的Intent中加入此標記

5.0之前,Activity1用該flag啟動Activity2在OverviewScreen中是沒有分開的.也就是說如果back到后臺后,再通過launcher中點擊app的icon進入,將直接進入Activity1,并且無法回到activity2的界面.
5.0之后,OverviewScreen中,會將兩個activity分開.可以返回指定想要的activity.
  1. FLAG_ACTIVITY_MULTIPLE_TASK

> 不建議使用此標記,除非你自己實現了應用程序的啟動器。結合FLAG_ACTIVITY_NEW_TASK這個標記,即使要啟動的activity已經存在一個task在運行,也會新啟動一個task來運行要啟動的activity

系統缺省是不帶任務管理器的,所以當你使用這個標簽的時候,你必須確保你能從你啟動的task中返回回來。
如果沒有設置FLAG_ACTIVITY_NEW_TASK,這個標記被忽略

  1. FLAG_ACTIVITY_CLEAR_TASK

> 文檔原文:`If set in an Intent passed to Context.startActivity(), this flag will cause any existing task that would be associated with the activity to be cleared before the activity is started. That is, the activity becomes the new root of an otherwise empty task, and any old activities are finished. This can only be used in conjunction with FLAG_ACTIVITY_NEW_TASK.` 
個人翻譯:`這個flag會導致,在這個activity啟動之前,任何與該activity相關的task都會被清除.也就是說,這個activity將會是一個空task的最底部的activity,之前所有的activity都會被finish掉.這個flag只能和FLAG_ACTIVITY_NEW_TASK結合使用.`
比如說原來棧中情況是`A,B,C,D`,在D中啟動B(加入該flag),中間過程是`A,B,C`依次destory,D先onPause,隨后BonCreate,onStart,onResume.D再onStop,onDestory.最后只有一個B在棧底.(無論taskAffinity..?)
  1. FLAG_ACTIVITY_SINGLE_TOP

> 相當于launchMode中的singleTop,比如說原來棧中情況是`A,B,C,D`,在D中啟動D(加入該flag),棧中的情況還是`A,B,C,D`.
  1. FLAG_ACTIVITY_CLEAR_TOP

> 不同于launchMode中的singleTask,比如說原來棧中情況是`A,B,C,D`,在D中啟動B(加入該flag), 棧中的情況將為`A,B`.但是B會重新onCreate()...,并沒有執行onNewIntent().如果希望與singleTask效果相同,可以加入`FLAG_ACTIVITY_SINGLE_TOP`.
  1. FLAG_ACTIVITY_REORDER_TO_FRONT

> 這個跟上邊FLAG_ACTIVITY_BROUGHT_TO_FRONT的是容易混淆的.比如說原來棧中情況是`A,B,C,D`,在D中啟動B(加入該flag),棧中的情況會是`A,C,D,B`.(調用onNewIntent())
  1. FLAG_ACTIVITY_BROUGHT_TO_FRONT

> 這個是最容易讓人誤解的flag了.跟FLAG_ACTIVITY_REORDER_TO_FRONT是不一樣的.不是由我們一般開發者使用的flag.
文檔中解釋:This flag is not normally set by application code, but set for you by the system as described in the launchMode documentation for the singleTask mode.
  1. FLAG_ACTIVITY_NO_HISTORY

> A啟動B(加入該Flag),B啟動C.在C返回,將直接返回到A.B在A正常onResume后,才會調用`onStop,onDestory...`
而且被這個flag啟動的activity,它的onActivityResult()永遠不會被調用
  1. FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

> 我所理解的,加了這個flag啟動的activity所在的task(必須是該task中最底部的activity)將不會在多任務界面出現.一般配合FLAG_ACTIVITY_NEW_TASK使用,這樣新的任務棧,在最近使用列表中,就不會出現.
  1. FLAG_ACTIVITY_FORWARD_RESULT

> 多個Activity的值傳遞。A通過startActivityForResult啟動B,B啟動C,但B為過渡頁可以finish了,A在期望C把結果返回.這種情況,B可以在啟動C的時候加入該flag.
  1. FLAG_ACTIVITY_NO_USER_ACTION

> 禁止activity調用onUserLeaveHint()。
  onUserLeaveHint()作為activity周期的一部分,它在activity因為用戶要跳轉到別的activity而退到background時使用。比如,在用戶按下Home鍵(用戶的操作),它將被調用。比如有電話進來(不屬于用戶的操作),它就不會被調用。注意:通過調用finish()時該activity銷毀時不會調用該函數。 
  1. FLAG_ACTIVITY_RETAIN_IN_RECENTS (API21)

> 與activity設置autoRemoveFromRecents = false屬性效果一樣.是指當前activity銷毀后,是否還在概覽屏幕中顯示.(5.0之后生效)
  1. FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

> 一般為系統使用,比如要把一個應用從后臺移到前臺,有兩種方式:從多任務列表中恢復(不包含該flag);從啟動器中點擊icon恢復(包含該flag);需結合` FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | FLAG_ACTIVITY_NEW_DOCUMENT (API21)`理解
  1. FLAG_ACTIVITY_PREVIOUS_IS_TOP

> 即 A---> B --->C,若B啟動C時用了這個標志位,那在啟動時B并不會被當作棧頂的Activity,而是用A做棧頂來啟動C。此過程中B充當一個跳轉頁面。

典型的場景是在應用選擇頁面,如果在文本中點擊一個網址要跳轉到瀏覽器,而系統中又裝了不止一個瀏覽器應用,此時會彈出應用選擇頁面。在應用選擇頁面選擇某一款瀏覽器啟動時,就會用到這個Flag。然后應用選擇頁面將自己finish,以保證從瀏覽器返回時不會在回到選擇頁面。
經常與FLAG_ACTIVITY_FORWARD_RESULT 一起使用。

  1. FLAG_ACTIVITY_TASK_ON_HOME

> 該flag啟動的activity,點擊返回鍵會回到launcher.需要與`FLAG_ACTIVITY_NEW_TASK`一起使用,并且`FLAG_ACTIVITY_NEW_TASK`模式生效(參考該屬性)后,該flag才會起作用.
  1. FLAG_EXCLUDE_STOPPED_PACKAGES

> 設置之后,Intent就不會再匹配那些當前被停止的包里的組件。如果沒有設置,默認的匹配行為會包含這些被停止的包。
  1. FLAG_DEBUG_LOG_RESOLUTION

> debug模式可以打印log

Activity的task相關屬性

參考: 基礎總結篇之三:Activity的task相關

  1. allowTaskReparenting

> 這個屬性用來標記一個Activity實例在當前應用退到后臺后,是否能從啟動它的那個task移動到有共同affinity的task,“true”表示可以移動,“false”表示它必須呆在當前應用的task中,默認值為false。
比如在app1的activityA中打開app2的activity2,按home鍵,回到后臺后,這時在launcher中點擊App1,頁面顯示的是app1的activityA(是在此時將activity2轉移到app2的task中).再點擊app2,則顯示的是activity2,點擊back,則會在app2所在的任務棧中回退.

需要注意的是,如果app1退居后臺之后,沒有再次啟動app1,而是直接啟動app2,將不會出現以上現象。重新宿主的動作發生在appB再次啟動的過程中

  1. alwaysRetainTaskState

> 這個屬性用來標記應用的task是否保持原來的狀態,“true”表示總是保持,“false”表示不能夠保證,默認為“false”。此屬性只對task的根Activity起作用,其他的Activity都會被忽略。

默認情況下,如果一個應用在后臺呆的太久例如30分鐘,用戶從主選單再次選擇該應用時,系統就會對該應用的task進行清理,除了根Activity,其他Activity都會被清除出棧,但是如果在根Activity中設置了此屬性之后,用戶再次啟動應用時,仍然可以看到上一次操作的界面。
這個屬性對于一些應用非常有用,例如Browser應用程序,有很多狀態,比如打開很多的tab,用戶不想丟失這些狀態,使用這個屬性就極為恰當。

  1. clearTaskOnLaunch

> 這個屬性用來標記是否從task清除除根Activity之外的所有的Activity,“true”表示清除,“false”表示不清除,默認為“false”。同樣,這個屬性也只對根Activity起作用,其他的Activity都會被忽略。 如果設置了這個屬性為“true”,每次用戶重新啟動這個應用時,都只會看到根Activity,task中的其他Activity都會被清除出棧。
  1. finishOnTaskLaunch

> 與allowReparenting屬性相似,不同之處在于allowReparenting屬性是重新宿主到有共同affinity的task中,而finishOnTaskLaunch屬性是銷毀實例。如果這個屬性和android:allowReparenting都設定為“true”,則這個屬性優先級高。
  1. documentLaunchMode

#### intoExisting
> 該 Activity 會對文檔重復使用現有任務。這與不設置 FLAG_ACTIVITY_MULTIPLE_TASK 標志、但設置 FLAG_ACTIVITY_NEW_DOCUMENT 標志所產生的效果相同,如上文的使用 Intent 標志添加任務中所述。

#### always
> 該 Activity 為文檔創建新任務,即便文檔已打開也是如此。使用此值與同時設置 FLAG_ACTIVITY_NEW_DOCUMENT 和 FLAG_ACTIVITY_MULTIPLE_TASK 標志所產生的效果相同。

#### none
> 該 Activity 不會為文檔創建新任務。概覽屏幕將按其默認方式對待此 Activity:為應用顯示單個任務,該任務將從用戶上次調用的任意 Activity 開始繼續執行。

#### never
> 該 Activity 不會為文檔創建新任務。設置此值會替代 FLAG_ACTIVITY_NEW_DOCUMENT 和 FLAG_ACTIVITY_MULTIPLE_TASK 標志的行為(如果在 Intent 中設置了其中一個標志),并且概覽屏幕將為應用顯示單個任務,該任務將從用戶上次調用的任意 Activity 開始繼續執行。

對于除 none 和 never 以外的值,必須使用 launchMode="standard" 定義 Activity。如果未指定此屬性,則使用 documentLaunchMode="none"。

引用

meizixiongActivity的四種啟動模式
android 任務棧及啟動模式
android的task任務棧
Intent Flag的幾種介紹

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,156評論 6 529
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,866評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 174,880評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,398評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,202評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,743評論 1 320
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,822評論 3 438
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 41,962評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,476評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,444評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,579評論 1 365
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,129評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,840評論 3 344
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,231評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,487評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,177評論 3 388
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,568評論 2 370

推薦閱讀更多精彩內容