本篇文章主要內容來自於Android Doc,我翻譯以後又作了些加工,英文好的朋友也能夠直接去讀原文。
html
http://developer.android.com/guide/components/tasks-and-back-stack.htmlandroid
一個應用程序當中一般都會包含不少個Activity,每一個Activity都應該設計成爲一個具備特定的功能,而且可讓用戶進行操做的組件。另外,Activity之間還應該是能夠相互啓動的。好比,一個郵件應用中可能會包含一個用於展現郵件列表的Activity,而當用戶點擊了其中某一封郵件的時候,就會打開另一個Activity來顯示該封郵件的具體內容。瀏覽器
除此以外,一個Activity甚至還能夠去啓動其它應用程序當中的Activity。打個比方,若是你的應用但願去發送一封郵件,你就能夠定義一個具備"send"動做的Intent,而且傳入一些數據,如對方郵箱地址、郵件內容等。這樣,若是另一個應用程序中的某個Activity聲明本身是能夠響應這種Intent的,那麼這個Activity就會被打開。在當前場景下,這個Intent是爲了要發送郵件的,因此說郵件應用程序當中的編寫郵件Activity就應該被打開。當郵件發送出去以後,仍然仍是會回到你的應用程序當中,這讓用戶看起來好像剛纔那個編寫郵件的Activity就是你的應用程序當中的一部分。因此說,即便有不少個Activity分別都是來自於不一樣應用程序的,Android系統仍然能夠將它們無縫地結合到一塊兒,之因此能實現這一點,就是由於這些Activity都是存在於一個相同的任務(Task)當中的。數據結構
任務是一個Activity的集合,它使用棧的方式來管理其中的Activity,這個棧又被稱爲返回棧(back stack),棧中Activity的順序就是按照它們被打開的順序依次存放的。ide
手機的Home界面是大多數任務開始的地方,當用戶在Home界面上點擊了一個應用的圖標時,這個應用的任務就會被轉移到前臺。若是這個應用目前並無任何一個任務的話(說明這個應用最近沒有被啓動過),系統就會去建立一個新的任務,而且將該應用的主Activity放入到返回棧當中。測試
當一個Activity啓動了另一個Activity的時候,新的Activity就會被放置到返回棧的棧頂並將得到焦點。前一個Activity仍然保留在返回棧當中,但會處於中止狀態。當用戶按下Back鍵的時候,棧中最頂端的Activity會被移除掉,而後前一個Activity則會得從新回到最頂端的位置。返回棧中的Activity的順序永遠都不會發生改變,咱們只能向棧頂添加Activity,或者將棧頂的Activity移除掉。所以,返回棧是一個典型的後進先出(last in, first out)的數據結構。下圖經過時間線的方式很是清晰地向咱們展現了多個Activity在返回棧當中的狀態變化:ui
若是用戶一直地按Back鍵,這樣返回棧中的Activity會一個個地被移除,直到最終返回到主屏幕。當返回棧中全部的Activity都被移除掉的時候,對應的任務也就不存在了。spa
任務除了能夠被轉移到前臺以外,固然也是能夠被轉移到後臺的。當用戶開啓了一個新的任務,或者點擊Home鍵回到主屏幕的時候,以前任務就會被轉移到後臺了。當任務處於後臺狀態的時候,返回棧中全部的Activity都會進入中止狀態,但這些Activity在棧中的順序都會原封不動地保留着,以下圖所示:.net
這個時候,用戶還能夠將任意後臺的任務切換到前臺,這樣用戶應該就會看到以前離開這個任務時處於最頂端的那個Activity。舉個例子來講,當前任務A的棧中有三個Activity,如今用戶按下Home鍵,而後點擊桌面上的圖標啓動了另一個應用程序。當系統回到桌面的時候,其實任務A就已經進入後臺了,而後當另一個應用程序啓動的時候,系統會爲這個程序開啓一個新的任務(任務B)。當用戶使用完這個程序以後,再次按下Home鍵回到桌面,這個時候任務B也進入了後臺。而後用戶又從新打開了第一次使用的程序,這個時候任務A又會回到前臺,A任務棧中的三個Activity仍然會保留着剛纔的順序,最頂端的Activity將從新變爲運行狀態。以後用戶仍然能夠經過Home鍵或者多任務鍵來切換回任務B,或者啓動更多的任務,這就是Android中多任務切換的例子。翻譯
因爲返回棧中的Activity的順序永遠都不會發生改變,因此若是你的應用程序中容許有多個入口均可以啓動同一個Activity,那麼每次啓動的時候就都會建立該Activity的一個新的實例,而不是將下面的Activity的移動到棧頂。這樣的話就容易致使一個問題的產生,即同一個Activity有可能會被實例化不少次,以下圖所示:
可是呢,若是你不但願同一個Activity能夠被屢次實例化,那固然也是能夠的,立刻咱們就將開始討論若是實現這一功能,如今咱們先把默認的任務和Activity的行爲簡單歸納一下:
當Activity A啓動Activity B時,Activity A進入中止狀態,但系統仍然會將它的全部相關信息保留,好比滾動的位置,還有文本框輸入的內容等。若是用戶在Activity B中按下Back鍵,那麼Activity A將會從新回到運行狀態。
當用戶經過Home鍵離開一個任務時,該任務會進入後臺,而且返回棧中全部的Activity都會進入中止狀態。系統會將這些Activity的狀態進行保留,這樣當用戶下一次從新打開這個應用程序時,就能夠將後臺任務直接提取到前臺,並將以前最頂端的Activity進行恢復。
當用戶按下Back鍵時,當前最頂端的Activity會被從返回棧中移除掉,移除掉的Activity將被銷燬,而後前面一個Activity將處於棧頂位置並進入活動狀態。當一個Activity被銷燬了以後,系統不會再爲它保留任何的狀態信息。
每一個Activity均可以被實例化不少次,即便是在不一樣的任務當中。
Android系統管理任務和返回棧的方式,正如上面所描述的同樣,就是把全部啓動的Activity都放入到一個相同的任務當中,經過一個「後進先出」的棧來進行管理的。這種方式在絕大多數狀況下都是沒問題的,開發者也無須去關心任務中的Activity究竟是怎麼樣存放在返回棧當中的。可是呢,若是你想打破這種默認的行爲,好比說當啓動一個新的Activity時,你但願它能夠存在於一個獨立的任務當中,而不是現有的任務當中。或者說,當啓動一個Activity時,若是這個Activity已經存在於返回棧中了,你但願能把這個Activity直接移動到棧頂,而不是再建立一個它的實例。再或者,你但願能夠將返回棧中除了最底層的那個Activity以外的其它全部Activity所有清除掉。這些功能甚至更多功能,都是能夠經過在manifest文件中設置<activity>元素的屬性,或者是在啓動Activity時配置Intent的flag來實現的。
在<activity>元素中,有如下幾個屬性是可使用的:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
而在Intent當中,有如下幾個flag是比較經常使用的:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
下面咱們就將開始討論,如何經過manifest參數,以及Intent flag來改變Activity在任務中的默認行爲。
啓動模式容許你去定義如何將一個Activity的實例和當前的任務進行關聯,你能夠經過如下兩種不一樣的方式來定義啓動模式:
1.使用manifest文件
當你在manifest文件中聲明一個Activity的時候,你能夠指定這個Activity在啓動的時候該如何與任務進行關聯。
2.使用Intent flag
當你調用startActivity()方法時,你能夠在Intent中加入一個flag,從而指定新啓動的Activity該如何與當前任務進行關聯。
也就是說,若是Activity A啓動了Activity B,Activity B能夠定義本身該如何與當前任務進行關聯,而Activity A也能夠要求Activity B該如何與當前任務進行關聯。若是Activity B在manifest中已經定義了該如何與任務進行關聯,而Activity A同時也在Intent中要求了Activity B該怎麼樣與當前任務進行關聯,那麼此時Intent中的定義將覆蓋manifest中的定義。
須要注意的是,有些啓動模式在manifest中能夠指定,但在Intent中是指定不了的。一樣,也有些啓動模式在Intent中能夠指定,但在manifest中是指定不了的,下面咱們就來具體討論一下。
當在manifest文件中定義Activity的時候,你能夠經過<activity>元素的launchMode屬性來指定這個Activity應該如何與任務進行關聯。launchMode屬性一共有如下四種可選參數:
"standard"(默認啓動模式)
standard是默認的啓動模式,即若是不指定launchMode屬性,則自動就會使用這種啓動模式。這種啓動模式表示每次啓動該Activity時系統都會爲建立一個新的實例,而且總會把它放入到當前的任務當中。聲明成這種啓動模式的Activity能夠被實例化屢次,一個任務當中也能夠包含多個這種Activity的實例。
"singleTop"
這種啓動模式表示,若是要啓動的這個Activity在當前任務中已經存在了,而且還處於棧頂的位置,那麼系統就不會再去建立一個該Activity的實例,而是調用棧頂Activity的onNewIntent()方法。聲明成這種啓動模式的Activity也能夠被實例化屢次,一個任務當中也能夠包含多個這種Activity的實例。
舉個例子來說,一個任務的返回棧中有A、B、C、D四個Activity,其中A在最底端,D在最頂端。這個時候若是咱們要求再啓動一次D,而且D的啓動模式是"standard",那麼系統就會再建立一個D的實例放入到返回棧中,此時棧內元素爲:A-B-C-D-D。而若是D的啓動模式是"singleTop"的話,因爲D已是在棧頂了,那麼系統就不會再建立一個D的實例,而是直接調用D Activity的onNewIntent()方法,此時棧內元素仍然爲:A-B-C-D。
"singleTask"
這種啓動模式表示,系統會建立一個新的任務,並將啓動的Activity放入這個新任務的棧底位置。可是,若是現有任務當中已經存在一個該Activity的實例了,那麼系統就不會再建立一次它的實例,而是會直接調用它的onNewIntent()方法。聲明成這種啓動模式的Activity,在同一個任務當中只會存在一個實例。注意這裏咱們所說的啓動Activity,都指的是啓動其它應用程序中的Activity,由於"singleTask"模式在默認狀況下只有啓動其它程序的Activity纔會建立一個新的任務,啓動本身程序中的Activity仍是會使用相同的任務,具體緣由會在下面 處理affinity 部分進行解釋。
"singleInstance"
這種啓動模式和"singleTask"有點類似,只不過系統不會向聲明成"singleInstance"的Activity所在的任務當中再添加其它Activity。也就是說,這種Activity所在的任務中始終只會有一個Activity,經過這個Activity再打開的其它Activity也會被放入到別的任務當中。
再舉一個例子,Android系統內置的瀏覽器程序聲明本身瀏覽網頁的Activity始終應該在一個獨立的任務當中打開,也就是經過在<activity>元素中設置"singleTask"啓動模式來實現的。這意味着,當你的程序準備去打開Android內置瀏覽器的時候,新打開的Activity並不會放入到你當前的任務中,而是會啓動一個新的任務。而若是瀏覽器程序在後臺已經存在一個任務了,則會把這個任務切換到前臺。
其實無論是Activity在一個新任務當中啓動,仍是在當前任務中啓動,返回鍵永遠都會把咱們帶回到以前的一個Activity中的。可是有一種狀況是比較特殊的,就是若是Activity指定了啓動模式是"singleTask",而且啓動的是另一個應用程序中的Activity,這個時候當發現該Activity正好處於一個後臺任務當中的話,就會直接將這整個後臺任務一塊兒切換到前臺。此時按下返回鍵會優先將目前最前臺的任務(剛剛從後臺切換到最前臺)進行回退,下圖比較形象地展現了這種狀況:
更多關於如何在manifest文件中使用啓動模式的講解,能夠去參考《第一行代碼——Android》第二章部分的內容。
除了使用manifest文件以外,你也能夠在調用startActivity()方法的時候,爲Intent加入一個flag來改變Activity與任務的關聯方式,下面咱們來一一講解一下每種flag的做用:
FLAG_ACTIVITY_NEW_TASK
設置了這個flag,新啓動Activity就會被放置到一個新的任務當中(與"singleTask"有點相似,但不徹底同樣),固然這裏討論的仍然仍是啓動其它程序中的Activity。這個flag的做用一般是模擬一種Launcher的行爲,即列出一推能夠啓動的東西,但啓動的每個Activity都是在運行在本身獨立的任務當中的。
FLAG_ACTIVITY_SINGLE_TOP
設置了這個flag,若是要啓動的Activity在當前任務中已經存在了,而且還處於棧頂的位置,那麼就不會再次建立這個Activity的實例,而是直接調用它的onNewIntent()方法。這種flag和在launchMode中指定"singleTop"模式所實現的效果是同樣的。
FLAG_ACTIVITY_CLEAR_TOP
設置了這個flag,若是要啓動的Activity在當前任務中已經存在了,就不會再次建立這個Activity的實例,而是會把這個Activity之上的全部Activity所有關閉掉。好比說,一個任務當中有A、B、C、D四個Activity,而後D調用了startActivity()方法來啓動B,並將flag指定成FLAG_ACTIVITY_CLEAR_TOP,那麼此時C和D就會被關閉掉,如今返回棧中就只剩下A和B了。
那麼此時Activity B會接收到這個啓動它的Intent,你能夠決定是讓Activity B調用onNewIntent()方法(不會建立新的實例),仍是將Activity B銷燬掉並從新建立實例。若是Activity B沒有在manifest中指定任何啓動模式(也就是"standard"模式),而且Intent中也沒有加入一個FLAG_ACTIVITY_SINGLE_TOP flag,那麼此時Activity B就會銷燬掉,而後從新建立實例。而若是Activity B在manifest中指定了任何一種啓動模式,或者是在Intent中加入了一個FLAG_ACTIVITY_SINGLE_TOP flag,那麼就會調用Activity B的onNewIntent()方法。
FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK結合在一塊兒使用也會有比較好的效果,好比能夠將一個後臺運行的任務切換到前臺,並把目標Activity之上的其它Activity所有關閉掉。這個功能在某些狀況下很是有用,好比說從通知欄啓動Activity的時候。
affinity能夠用於指定一個Activity更加願意依附於哪個任務,在默認狀況下,同一個應用程序中的全部Activity都具備相同的affinity,因此,這些Activity都更加傾向於運行在相同的任務當中。固然了,你也能夠去改變每一個Activity的affinity值,經過<activity>元素的taskAffinity屬性就能夠實現了。
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就會被清除掉。