Android任務、啓動模式、返回棧解析

任務和返回棧

一個應用程序當中通常都會包含很多個Activity,每個Activity都應該設計成爲一個具有特定的功能,並且可以讓用戶進行操作的組件。另外,Activity之間還應該是可以相互啓動的。比如,一個郵件應用中可能會包含一個用於展示郵件列表的Activity,而當用戶點擊了其中某一封郵件的時候,就會打開另外一個Activity來顯示該封郵件的具體內容。

除此之外,一個Activity甚至還可以去啓動其它應用程序當中的Activity。打個比方,如果你的應用希望去發送一封郵件,你就可以定義一個具有"send"動作的Intent,並且傳入一些數據,如對方郵箱地址、郵件內容等。這樣,如果另外一個應用程序中的某個Activity聲明自己是可以響應這種Intent的,那麼這個Activity就會被打開。在當前場景下,這個Intent是爲了要發送郵件的,所以說郵件應用程序當中的編寫郵件Activity就應該被打開。當郵件發送出去之後,仍然還是會回到你的應用程序當中,這讓用戶看起來好像剛纔那個編寫郵件的Activity就是你的應用程序當中的一部分。所以說,即使有很多個Activity分別都是來自於不同應用程序的,Android系統仍然可以將它們無縫地結合到一起,之所以能實現這一點,就是因爲這些Activity都是存在於一個相同的任務(Task)當中的。

任務是一個Activity的集合,它使用棧的方式來管理其中的Activity,這個棧又被稱爲返回棧(back stack),棧中Activity的順序就是按照它們被打開的順序依次存放的。

手機的Home界面是大多數任務開始的地方,當用戶在Home界面上點擊了一個應用的圖標時,這個應用的任務就會被轉移到前臺。如果這個應用目前並沒有任何一個任務的話(說明這個應用最近沒有被啓動過),系統就會去創建一個新的任務,並且將該應用的主Activity放入到返回棧當中。

當一個Activity啓動了另外一個Activity的時候,新的Activity就會被放置到返回棧的棧頂並將獲得焦點。前一個Activity仍然保留在返回棧當中,但會處於停止狀態。當用戶按下Back鍵的時候,棧中最頂端的Activity會被移除掉,然後前一個Activity則會得重新回到最頂端的位置。返回棧中的Activity的順序永遠都不會發生改變,我們只能向棧頂添加Activity,或者將棧頂的Activity移除掉。因此,返回棧是一個典型的後進先出(last in, first out)的數據結構。下圖通過時間線的方式非常清晰地向我們展示了多個Activity在返回棧當中的狀態變化:


如果用戶一直地按Back鍵,這樣返回棧中的Activity會一個個地被移除,直到最終返回到主屏幕。當返回棧中所有的Activity都被移除掉的時候,對應的任務也就不存在了。

任務除了可以被轉移到前臺之外,當然也是可以被轉移到後臺的。當用戶開啓了一個新的任務,或者點擊Home鍵回到主屏幕的時候,之前任務就會被轉移到後臺了。當任務處於後臺狀態的時候,返回棧中所有的Activity都會進入停止狀態,但這些Activity在棧中的順序都會原封不動地保留着,如下圖所示:


這個時候,用戶還可以將任意後臺的任務切換到前臺,這樣用戶應該就會看到之前離開這個任務時處於最頂端的那個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文件

當在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》第二章部分的內容。

使用Intent flags

除了使用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

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就會被清除掉。


轉自:http://blog.csdn.net/guolin_blog/article/details/41087993

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章