天天看點

Android關于Activity知識點總結(二)任務、傳回棧與啟動模式

以下内容為複習總結,若有幸被大神看到,望指正其不準,補充其不足。萬分感謝!!!

Android關于Activity知識點總結(一)生命周期與狀态及狀态儲存

一、任務(task)和傳回棧(back stack)概述

說到任務不得不說android應用程式是由若幹個Activity組成,每個Activity都可以設定各自特定的功能,來與使用者進行互動操作,本應用中Activity之間可以互相開啟,并傳遞一些消息或者資料。當然也可以通過設定Intent的action來打開第三方的應用的Activity,而當finish掉這個(或多個)Activity(無論是本應用的還是第三方應用的)時,仍然可以回到上一個Activity。是因為android将這些Activity都存在于一個相同的任務(task)中。

任務(task):是使用者在執行某些工作時與之互動的Activity的集合。它使用棧來管理這些Activity,而這個棧就成為傳回棧(back stack)

傳回棧的特點:後進先出,棧中的Activity永遠不會被重新排列,隻會從棧頂中壓入或彈出。

二、任務和傳回棧(back stack)工作流程

(一)單任務時:

當使用者點選home主屏的應用程式圖示時,這個應用的任務就會轉移到前台,如果目前應用沒有任何任務,則說明此應用最近還沒有被使用過,這時将會為此應用建立一個新的任務,并将該應用的主Activity放入傳回棧中作為根activity。

此時在目前Activity中打開另一個Activity時,新的Activity将被推到這個傳回棧的棧頂,并獲得焦點,之前的Activity仍然在傳回棧中,但處于Stopped狀态,此時系統将保留其使用者界面的目前狀态,當使用者按下傳回鍵或者手動調用finish,棧中最頂端的Activity會被從棧中移除,此時将之前的Activity重新置回棧頂位置,恢複之前狀态,獲得焦點。下圖為按時間軸的方式顯示了Activity在傳回棧中的狀态變化:

Android關于Activity知識點總結(二)任務、傳回棧與啟動模式

當使用者連續按下傳回鍵時,那麼棧中的每一個Activity都将從棧頂一個一個彈出,以顯示前一個Activity,知道最終傳回到主螢幕。當所有的Activity都才棧中移除掉時,此時棧為空,那麼對應的任務也就不再存在。

(二)、多任務時

任務除了可以被轉移到前台之外,還可以将其轉移到背景。當開啟一個新的任務,或者按home鍵傳回主螢幕時,之前的任務就會被轉移到背景,當任務處于背景時,傳回棧中的所有Activity都将進入停止Stopped狀态,但這些Activity在棧中的順序和狀态不會改變,隻是失去了焦點。

下圖就是多任務前背景的展示:

Android關于Activity知識點總結(二)任務、傳回棧與啟動模式

使用者可以對任意任務做前台與背景的切換,如上圖任務B此時正在前台與使用者互動,而任務A在背景處于停止狀态,等待恢複。當使用者通過多任務鍵切換回任務A,則任務A轉移到前台,恢複之前狀态,獲得焦點,進行與客戶互動的操作,而任務B轉移到背景,處于停止狀态等待恢複;如果當使用者按home鍵回到主螢幕時,任務B也将轉移到背景,等待恢複。

注意:背景可以同時運作多個背景任務。由于android的回收(GC)機制,如果使用者同時運作多個背景任務,當記憶體緊張時,系統将會銷毀背景的Activity,以回收記憶體資源,進而導緻Activity狀态丢失。

由于傳回棧中Activity永遠不會重新排列的特點,是以如果應用運作使用者從多個入口打開指定Activity時,則會建立這個Activity的新實力,然後壓入棧頂,而不是将之前棧中有的執行個體放到棧頂。是以預設的任務中Activity會被多次執行個體化,如下圖所示:

Android關于Activity知識點總結(二)任務、傳回棧與啟動模式

如果你不希望在應用中同一個Activity被多次執行個體化,也是可以的,可以通過管理任務(下文)設定Activity的啟動模式或Intent的flag來控制。

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

  • 當Activity_A啟動Activity_B時,Activity_A将會停止,但系統會保留其狀态(如:滾動位置和輸入框的文本等)。如果使用者在處于Activity_B時按back按鈕時,則Activity_A将恢複其狀态,繼續執行。
  • 如果使用者按back傳回鍵時,則目前Activity将會從傳回棧中彈出并銷毀。傳回棧中的前一個Activity将恢複執行。銷毀Activity時,系統不會保留Activity的狀态。
  • 使用者通過按home鍵離開任務時,目前Activity将停止并且其任務将進入背景,系統會保留任務中所有Activity的狀态,如果使用者通過選擇開始任務啟動器的圖示來恢複任務,則任務将會出現在前台并恢複執行棧頂的Activity。
  • 即使來自其他任務,Activity也可以被多次執行個體化。

三、管理任務

(一)概述

Android系統管理任務和傳回棧的方式,如上文介紹,把所有連續啟動的Activity都放入一個相同的任務中,并通過一個具有“後進先出”特點的傳回棧管理。這種方式在絕大多數情況是沒有問題的,我們也無需關心任務中Activity是如何壓入和彈出棧的。但是如果你不想使用這種預設的行為,想根據自己意願去操作Activity,比如:當啟動一個新的Activity時,你希望它可以存在于一個獨立的任務當中,而不是在現有的任務中;或者,當啟動一個Activity時,如果這個Activity已經存在于傳回棧中,你希望把這個Activity直接移至棧頂,而不是再建立一個它的執行個體;再或者,你希望可以将傳回棧中除了最底層根Activity之外的其他所有Activity都移除出棧,等等這些功能,你都可以通過設定manifest清單檔案中<activity>元素的屬性,或者在啟動Activity時配置Intent的flag來實作。

  • 通過<activity>元素屬性控制時,可以使用的屬性如下:
  1. taskAffinity       此屬性控制Activity的親和力的,控制它依附于哪個任務
  2. launchMode     此屬性就是控制Activity的啟動模式的
  3. allowTaskReparenting       與1配合使用
  4. clearTaskOnLaunch          與5和6都是對傳回棧内Activity移除與否的控制
  5. alwaysRetainTaskState
  6. finishOnTaskLaunch
  • 通過Intent的flag控制時,常用的flag值如下:(在啟動模式中詳細介紹)
  1. FLAG_ACTIVITY_NEW_TASK         與“singleTask”類似,但不完全一樣
  2. FLAG_ACTIVITY_CLEAR_TOP       與“singleTop”效果一樣
  3. FLAG_ACTIVITY_SINGLE_TOP      這個會将它之上的Activity移除出棧

此處官方給出警告:

Caution: Most apps should not interrupt the default behavior for activities and tasks. If you determine that it's necessary for your activity to modify the default behaviors, use caution and be sure to test the usability of the activity during launch and when navigating back to it from other activities and tasks with the Back button. Be sure to test for navigation behaviors that might conflict with the user's expected behavior.

譯文:

警告:大多數應用都不得中斷 Activity 和任務的預設行為: 如果确定您的 Activity 必須修改預設行為,當使用“傳回”按鈕從其他 Activity 和任務導航回到該 Activity 時,請務必要謹慎并確定在啟動期間測試該 Activity 的可用性。請確定測試導航行為是否有可能與使用者的預期行為沖突。

(二)啟動模式

啟動模式是允許你去定義如何将一個Activity執行個體和目前任務進行關聯。你可以通過以下兩種方式進行定義啟動模式:

  • 使用清單檔案

在清單檔案manifest中使用<activity>的launchMode屬性值,來指定Activity啟動時如何與任務關聯。

  • 使用Intent的flag

調用startActivity()方法時,可以在Intent中加入flag标記,來聲明新的Activity如何(是否)與目前任務關聯。

注:

  1. 在Intent中定義的方式優先級要高于在清單檔案中定義的方式。比如:如果Activity_A啟動Activity_B,Activity_B在清單檔案中定義它應該與目前任務如何關聯,并且Activity_A也在啟動Activity_B時在Intent的flag中要求B該如何與目前任務如何關聯,那麼此時Intent定義的方式将覆寫掉清單檔案中定義的的方式。
  2. 有些啟動模式在清單檔案manifest中可以定義,但在Intent中無法定義;同樣,有些啟動模式在Intent中可以定義,但在清單檔案manifest中無法定義。即兩個設定啟動模式的方法中定義的方式并不完全一樣。

使用清單manifest檔案

通過對<activity>标簽中android:launchMode="以下參數"屬性設定,此屬性共有四種參數可供選擇:

  • “standard”(預設啟動模式)

這是标準模式,也是系統預設的啟動模式。如同上邊介紹,即使不定義launchMode屬性,系統也會自動使用這種模式。這種模式每次啟動Activity時,不管傳回棧有沒有這個Activity的執行個體存在,都會建立新Activity的執行個體,并把它放入目前的任務中。這種啟動模式中,Activityke可以被多次執行個體化,在傳回棧中也會有多個這種Activity的執行個體。

下圖為此模式下傳回棧中執行個體

Android關于Activity知識點總結(二)任務、傳回棧與啟動模式

小結:此模式下Activity每次都會建立執行個體,走完整的生命周期,這樣相對很消耗系統資源。

  • “singleTop”

此模式是棧頂複用模式,從名字可以看出當要啟動此模式下的Activity時,如果此Activity已經在傳回棧中,并且處于棧頂位置,那麼啟動此Activity時将不會重新建立該Activity的執行個體,而是直接複用棧頂的這個Activity;當然如果此Activity沒有在傳回棧棧頂,或者在傳回棧中沒有此Activity的執行個體,那麼都将重新建立此Activity的執行個體,壓入棧頂。

實作複用的方式:當Activity在棧頂時,開啟此Activity,會調用此Activity的onNewIntent(Intent intent)(将在文末介紹)方法,此方法會在onPause()方法之前調用,而不會再走onCreate-onStart-onResume這個生命周期。

注意:為某個 Activity 建立新執行個體時,使用者可以按“傳回”按鈕傳回到前一個 Activity。 但是,當 Activity 的現有執行個體處理新 Intent 時,則在新 Intent 到達 

onNewIntent()

 之前,使用者無法按“傳回”按鈕傳回到 Activity 的狀态。

下圖為Activity_B在此模式下傳回棧中執行個體:

Android關于Activity知識點總結(二)任務、傳回棧與啟動模式

小結:此模式下的Activity在傳回棧中也可以被多次執行個體化,前提條件是:這個Activity執行個體不在傳回棧的棧頂時。是以傳回棧中可以有多個此Activity的執行個體。

使用場景:這種模式一般運用在一個Activity被頻繁推到棧頂的情況,比如IM(即時通訊)聊天,有很多消息過來了,不可能每點選一個消息就去建立一個Activity;新聞推送,也不可能每次點選推送消息,就去建立一個Activity

  • “singleTask”

此模式也可以叫棧内複用,此模式下傳回棧中隻有一個此Activity的執行個體。開啟此模式下的Activity時,系統會首先會判斷是否有其需要的傳回棧,1、如果沒有,那麼就建立傳回棧并建立該Activity的執行個體壓入棧内;2、如果有需要的傳回棧,但是棧内沒有此Activity的執行個體,那麼就建立此Activity的執行個體并壓入棧内;3、如果有需要的傳回棧,并且棧内有次Activity的執行個體,那麼無論次Activity實在棧頂,還是在棧内其他位置,都将複用此執行個體,并将此Activity執行個體之上的其他Activity執行個體移除出棧,将此執行個體至于棧頂,此時會調用Activity的onNewIntent(Intent intent)方法。

如何判斷是都有此Activity執行個體需要的傳回棧呢:

(a)這裡就要說到Activity的taskAffinity屬性了,此屬性是設定Activity和任務關聯的,系統通過這個值判斷是否是這個Activity需要的傳回棧。Android系統會檢測要啟動的Activity的affinity和目前任務的affinity是否相同?如果相同,則是目前Activity需要的任務(即傳回棧);如果不同,則會建立一個任務,并将該任務task的taskAffinity設定為此Activity的taskActivity。

 (b)同一個應用中所有Activity的affinity都是預設相同的,都是自己的包名;是以不同的程式間是Activity的affinity是不相同的,是以啟動自己的此模式下的Activity時是不會建立任務的,而啟動其他應用的此模式下的Activity時,需要建立一個任務。

下圖為Activity_B和Activity_E在此模式下傳回棧的情況:

Android關于Activity知識點總結(二)任務、傳回棧與啟動模式

小結:在<activity>中不設定taskAffinity這個屬性,開啟本應用中此模式的Activity,會在棧中檢視是否有次Activity的執行個體?如果有,則不會建立此Activity的執行個體,而是将此Activity之上的其他Activity移除出傳回棧,并将此Activity置于棧頂,然後調用onNewIntent(Intent intent)方法;如果傳回棧中沒有此Activity的執行個體,那麼就建立此Activity的執行個體,并壓入棧頂。如果開啟的其他應用此模式的Activity,則新開啟一個任務,然後建立這個Activity的執行個體,然後壓入新任務的傳回棧棧頂。

注:盡管 Activity 在新任務中啟動,但是使用者按“傳回”按鈕仍會傳回到前一個 Activity_B。

  • “singleInstance”

這個模式也有稱它是單例模式。此模式和“singleTask”模式有點相似,隻不過此模式下的Activity會單獨在一個任務task中,且這個任務的傳回棧中隻有唯一的一個此Activity的執行個體,系統不會再向這個任務的傳回棧中添加其他Activity。

舉個例子:Android系統内置的浏覽器程式聲明自己浏覽網頁的Activity始終應該在一個獨立的任務當中打開,也就是通過在<activity>元素中設定"singleTask"啟動模式來實作的。這意味着,當你的程式準備去打開Android内置浏覽器的時候,新打開的Activity并不會放入到你目前的任務中,而是會啟動一個新的任務。而如果浏覽器程式在背景已經存在一個任務了,則會把這個任務切換到前台。

下圖Activity_B為此模式下的傳回棧:

Android關于Activity知識點總結(二)任務、傳回棧與啟動模式

小結:開啟此模式下的Activity,總是會為之建立一個任務task,此任務中的傳回棧中隻存放唯一一個此Activity的執行個體。而此時任務将會移至前台顯示,當點傳回鍵時,還會傳回之前的Activity;然而如圖又打開Activity_C時,則建立Activity_C的執行個體,将其Activity_C的執行個體壓入之前的任務傳回棧中,并将之前的任務至于前台,顯示棧頂的Activity_C,而此時點傳回鍵時,傳回的0傳回棧中Activity_C的上一個Activity_A。

下圖為官網給出多任務回退邏輯:

Android關于Activity知識點總結(二)任務、傳回棧與啟動模式

使用Intent的flag

使用方法就是在使用startActivity的時候建構Intent,對Intent加入一個flag來改變Activity與任務的關聯模式。

  • FLAG_ACTIVITY_NEW_TASK

設定這個flag這會産生與 

"singleTask"

值相同的行為。

啟動Activity時系統會查找此Activity與之關聯的任務,如果找不到就建立一個任務,然後建立此Activity的執行個體并壓入棧頂,然将此任務與此Activity關聯;如果找到與之關聯的任務,則檢視傳回棧中是否已存在此Activity的執行個體?如果不存在,就建立此Activity執行個體并壓入棧頂;如果存在,則将此Activity之上的其他Activity移除出棧,将此Activity置于棧頂,調用此Activity的onNewIntent(Intent intent)方法。

這個flag的作用通常是模拟一種Launcher的行為,即列出一推可以啟動的東西,但啟動的每一個Activity都是在運作在自己獨立的任務當中的。

  • FLAG_ACTIVITY_SINGLE_TOP

設定這種flag和在launchMode中指定"singleTop"模式所實作的效果是一樣的。

如果啟動的Activity執行個體在棧頂,則不建立執行個體,直接調用此Activity的onNewIntent(Intent intent)方法;不在棧頂就建立新的執行個體。

  • FLAG_ACTIVITY_CLEAR_TOP

設定這種flag的行為,在launchMode沒有與之對應的屬性,算是flag的特色吧。

前提:Activity_A通過startActivity()啟動flag指定成此模式的Activity_B,它分為以下兩種情況:

(a)當Activity_B在manifest中指定啟動模式“standard“(或不做任何設定),并且Intent的沒有FLAG_ACTIVITY_SINGLE_TOP:

此時啟動Activity_B,會在傳回棧中查找Activity_B的執行個體是否存在?如果不存在,則建立Activity_B的執行個體壓入棧頂;如果存在,則将Activity_B與它之上的所有Activity全部銷毀,然後建立Activity_B,放入棧頂。

(b)除以上情況外,無論當Activity_B在manifest中指定除哪種啟動模式,或和Intent的其他flag任意結合:

此時啟動Activity_B,同樣會在傳回棧中查找Activity_B的執行個體是否存在?如果不存在,則建立Activity_B的執行個體壓入棧頂;如果存在,則将Activity_B之上的所有Activity全部銷毀,然後複用Activity_B,調用Activity_B的onNewIntent()方法。

  • 通常FLAG_ACTIVITY_CLEAR_TOP與FLAG_ACTIVITY_NEW_TASK結合使用:

一起使用時,通過這些标志,可以找到其他任務中的現有 Activity,并将其放入可從中響應 Intent 的位置。比如可以将一個背景運作的任務切換到前台,并把目标Activity之上的其它Activity全部關閉掉。這個功能在某些情況下非常有用,比如說從通知欄啟動Activity的時候。

四、清單檔案manifest中其他屬性介紹

處理affinity(有将其翻譯為關聯)

affinity所表示的是一個Activity 更願意依附于哪個任務。預設情況下,同一應用中的所有 Activity 彼此關聯,也就是有相同的affinity值,即包名。 是以,預設情況下,同一應用中的所有 Activity 相同任務中,此任務和Activity的affinity都是包名。 不過,您可以修改每個Activity 的affinity值,更改Activity與任務的關聯。 在不同應用中定義的 Activity 可以共享關聯,或者可為在同一應用中定義的 Activity 配置設定不同的任務關聯。

可以通過更改<activity>元素的taskAffinity屬性來修改和任務的關聯,此屬性可以接受一個字元串,但字元串不可以為本應用的包名,因為系統會預設使用包名定義affinity值。

affinity主要有以下兩種應用場景:

  • 當調用startActivity()方法來啟動一個Activity時,預設是将它放入到目前的任務當中。但是,如果在Intent中加入了一個FLAG_ACTIVITY_NEW_TASK flag的話(或者該Activity在manifest檔案中聲明的啟動模式是"singleTask"),系統就會嘗試為這個Activity單獨建立一個任務。

    但是規則并不是隻有這麼簡單,系統會去檢測要啟動的這個Activity的affinity和目前任務的affinity是否相同,如果相同的話就會把它放入到現有任務當中,如果不同則會去建立一個新的任務。而同一個程式中所有Activity的affinity預設都是相同的,這也是前面為什麼說,同一個應用程式中即使聲明成"singleTask",也不會為這個Activity再去建立一個新的任務了。

  • 當把Activity的allowTaskReparenting屬性設定成true時,Activity就擁有了一個轉移所在任務的能力。具體點來說,就是一個Activity現在是處于某個任務當中的,但是它與另外一個任務具有相同的affinity值,那麼當另外這個任務切換到前台的時候,該Activity就可以轉移到現在的這個任務當中。

    那還是舉一個形象點的例子吧,比如有一個天氣預報程式,它有一個Activity是專門用于顯示天氣資訊的,這個Activity和該天氣預報程式的所有其它Activity具體相同的affinity值,并且還将allowTaskReparenting屬性設定成true了。這個時候,你自己的應用程式通過Intent去啟動了這個用于顯示天氣資訊的Activity,那麼此時這個Activity應該是和你的應用程式是在同一個任務當中的。但是當把天氣預報程式切換到前台的時候,這個Activity又會被轉移到天氣預報程式的任務當中,并顯示出來,因為它們擁有相同的affinity值,并且将allowTaskReparenting屬性設定成了true。

清空傳回棧

如果使用者将任務切換到背景之後過了很長一段時間,系統會将這個任務中除了最底層的那個Activity之外的其它所有Activity全部清除掉。當使用者重新回到這個任務的時候,最底層的那個Activity将得到恢複。這個是系統預設的行為,因為既然過了這麼長的一段時間,使用者很有可能早就忘記了當時正在做什麼,那麼重新回到這個任務的時候,基本上應該是要去做點新的事情了。

當然,既然說是預設的行為,那就說明我們肯定是有辦法來改變的,在<activity>元素中設定以下幾種屬性就可以改變系統這一預設行為:

  • alwaysRetainTaskState

如果将最底層的那個Activity的這個屬性設定為true,那麼上面所描述的預設行為就将不會發生,任務中所有的Activity即使過了很長一段時間之後仍然會被繼續保留。

  • clearTaskOnLaunch

如果将最底層的那個Activity的這個屬性設定為true,那麼隻要使用者離開了目前任務,再次傳回的時候就會将最底層Activity之上的所有其它Activity全部清除掉。簡單來講,就是一種和alwaysRetainTaskState完全相反的工作模式,它保證每次傳回任務的時候都會是一種初始化狀态,即使使用者僅僅離開了很短的一段時間。

  • finishOnTaskLaunch

這個屬性和clearTaskOnLaunch是比較類似的,不過它不是作用于整個任務上的,而是作用于單個Activity上。如果某個Activity将這個屬性設定成true,那麼使用者一旦離開了目前任務,再次傳回時這個Activity就會被清除掉。

五、onNewIntent(Intent intent)方法

  • 調用時機:

當開啟設定了啟動模式(“singleTop”、“singleTask”、“singleInstance”或設定Intent的flag)的Activity時,不會建立新的Activity執行個體,而是複用棧内已存在的執行個體,這時系統會讓此Activity執行個體回調此方法,來重新啟動它的Intent;

此方法在調用之前,Activity總是會被暫停或停止,是以onNewIntent()方法肯定會在onResume()方法之前被回調。具體要看Activity之前退到背景時Activity的狀态,如果退到背景時處于暫停狀态(即執行了onPause方法),此時重新開機Activity時會執行onNewIntent()-->onResume();如果退到背景時Activity處于停止狀态(即執行了onStop方法),此時重新開機Activity則會執行onNewIntent()-->onRestart()-->onStart()-->onResume()。

  • 注:

此時getIntent()獲得到的Intent仍然是之前老的Intent,如果你要獲得新的Intent,你需要在onNewIntent()方法中調用setIntent(Intent intent)來更新。

參考官網文檔:

https://developer.android.google.cn/guide/components/activities/tasks-and-back-stack