任務(wù)和返回棧
一個應(yīng)用程序當(dāng)中通常都會包含很多個Activity,每個Activity都應(yīng)該設(shè)計(jì)成為一個具有特定的功能,并且可以讓用戶進(jìn)行操作的組件。另外,Activity之間還應(yīng)該是可以相互啟動的。比如,一個郵件應(yīng)用中可能會包含一個用于展示郵件列表的Activity,而當(dāng)用戶點(diǎn)擊了其中某一封郵件的時候,就會打開另外一個Activity來顯示該封郵件的具體內(nèi)容。
除此之外,一個Activity甚至還可以去啟動其它應(yīng)用程序當(dāng)中的Activity。打個比方,如果你的應(yīng)用希望去發(fā)送一封郵件,你就可以定義一個具有"send"動作的Intent,并且傳入一些數(shù)據(jù),如對方郵箱地址、郵件內(nèi)容等。這樣,如果另外一個應(yīng)用程序中的某個Activity聲明自己是可以響應(yīng)這種Intent的,那么這個Activity就會被打開。在當(dāng)前場景下,這個Intent是為了要發(fā)送郵件的,所以說郵件應(yīng)用程序當(dāng)中的編寫郵件Activity就應(yīng)該被打開。當(dāng)郵件發(fā)送出去之后,仍然還是會回到你的應(yīng)用程序當(dāng)中,這讓用戶看起來好像剛才那個編寫郵件的Activity就是你的應(yīng)用程序當(dāng)中的一部分。所以說,即使有很多個Activity分別都是來自于不同應(yīng)用程序的,Android系統(tǒng)仍然可以將它們無縫地結(jié)合到一起,之所以能實(shí)現(xiàn)這一點(diǎn),就是因?yàn)檫@些Activity都是存在于一個相同的任務(wù)(Task)當(dāng)中的。
任務(wù)是一個Activity的集合,它使用棧的方式來管理其中的Activity,這個棧又被稱為返回棧(back stack),棧中Activity的順序就是按照它們被打開的順序依次存放的。
定義啟動模式
啟動模式允許你去定義如何將一個Activity的實(shí)例和當(dāng)前的任務(wù)進(jìn)行關(guān)聯(lián),你可以通過以下兩種不同的方式來定義啟動模式:
1.使用manifest文件
當(dāng)你在manifest文件中聲明一個Activity的時候,你可以指定這個Activity在啟動的時候該如何與任務(wù)進(jìn)行關(guān)聯(lián)。
2.使用Intent flag
當(dāng)你調(diào)用startActivity()方法時,你可以在Intent中加入一個flag,從而指定新啟動的Activity該如何與當(dāng)前任務(wù)進(jìn)行關(guān)聯(lián)。
也就是說,如果Activity A啟動了Activity B,Activity B可以定義自己該如何與當(dāng)前任務(wù)進(jìn)行關(guān)聯(lián),而Activity A也可以要求Activity B該如何與當(dāng)前任務(wù)進(jìn)行關(guān)聯(lián)。如果Activity B在manifest中已經(jīng)定義了該如何與任務(wù)進(jìn)行關(guān)聯(lián),而Activity A同時也在Intent中要求了Activity B該怎么樣與當(dāng)前任務(wù)進(jìn)行關(guān)聯(lián),那么此時Intent中的定義將覆蓋manifest中的定義。
需要注意的是,有些啟動模式在manifest中可以指定,但在Intent中是指定不了的。同樣,也有些啟動模式在Intent中可以指定,但在manifest中是指定不了的,下面我們就來具體討論一下。
使用manifest文件
當(dāng)在manifest文件中定義Activity的時候,你可以通過元素的launchMode屬性來指定這個Activity應(yīng)該如何與任務(wù)進(jìn)行關(guān)聯(lián)。launchMode屬性一共有以下四種可選參數(shù):
"standard"(默認(rèn)啟動模式)
standard是默認(rèn)的啟動模式,即如果不指定launchMode屬性,則自動就會使用這種啟動模式。這種啟動模式表示每次啟動該Activity時系統(tǒng)都會為創(chuàng)建一個新的實(shí)例,并且總會把它放入到當(dāng)前的任務(wù)當(dāng)中。聲明成這種啟動模式的Activity可以被實(shí)例化多次,一個任務(wù)當(dāng)中也可以包含多個這種Activity的實(shí)例。
"singleTop"
這種啟動模式表示,如果要啟動的這個Activity在當(dāng)前任務(wù)中已經(jīng)存在了,并且還處于棧頂?shù)奈恢?,那么系統(tǒng)就不會再去創(chuàng)建一個該Activity的實(shí)例,而是調(diào)用棧頂Activity的onNewIntent()方法。聲明成這種啟動模式的Activity也可以被實(shí)例化多次,一個任務(wù)當(dāng)中也可以包含多個這種Activity的實(shí)例。
舉個例子來講,一個任務(wù)的返回棧中有A、B、C、D四個Activity,其中A在最底端,D在最頂端。這個時候如果我們要求再啟動一次D,并且D的啟動模式是"standard",那么系統(tǒng)就會再創(chuàng)建一個D的實(shí)例放入到返回棧中,此時棧內(nèi)元素為:A-B-C-D-D。而如果D的啟動模式是"singleTop"的話,由于D已經(jīng)是在棧頂了,那么系統(tǒng)就不會再創(chuàng)建一個D的實(shí)例,而是直接調(diào)用D Activity的onNewIntent()方法,此時棧內(nèi)元素仍然為:A-B-C-D。
"singleTask"
這種啟動模式表示,系統(tǒng)會創(chuàng)建一個新的任務(wù),并將啟動的Activity放入這個新任務(wù)的棧底位置。但是,如果現(xiàn)有任務(wù)當(dāng)中已經(jīng)存在一個該Activity的實(shí)例了,那么系統(tǒng)就不會再創(chuàng)建一次它的實(shí)例,而是會直接調(diào)用它的onNewIntent()方法。聲明成這種啟動模式的Activity,在同一個任務(wù)當(dāng)中只會存在一個實(shí)例。注意這里我們所說的啟動Activity,都指的是啟動其它應(yīng)用程序中的Activity,因?yàn)?singleTask"模式在默認(rèn)情況下只有啟動其它程序的Activity才會創(chuàng)建一個新的任務(wù),啟動自己程序中的Activity還是會使用相同的任務(wù),具體原因會在下面處理affinity部分進(jìn)行解釋。
"singleInstance"
這種啟動模式和"singleTask"有點(diǎn)相似,只不過系統(tǒng)不會向聲明成"singleInstance"的Activity所在的任務(wù)當(dāng)中再添加其它Activity。也就是說,這種Activity所在的任務(wù)中始終只會有一個Activity,通過這個Activity再打開的其它Activity也會被放入到別的任務(wù)當(dāng)中。
再舉一個例子,Android系統(tǒng)內(nèi)置的瀏覽器程序聲明自己瀏覽網(wǎng)頁的Activity始終應(yīng)該在一個獨(dú)立的任務(wù)當(dāng)中打開,也就是通過在元素中設(shè)置"singleTask"啟動模式來實(shí)現(xiàn)的。這意味著,當(dāng)你的程序準(zhǔn)備去打開Android內(nèi)置瀏覽器的時候,新打開的Activity并不會放入到你當(dāng)前的任務(wù)中,而是會啟動一個新的任務(wù)。而如果瀏覽器程序在后臺已經(jīng)存在一個任務(wù)了,則會把這個任務(wù)切換到前臺。
其實(shí)不管是Activity在一個新任務(wù)當(dāng)中啟動,還是在當(dāng)前任務(wù)中啟動,返回鍵永遠(yuǎn)都會把我們帶回到之前的一個Activity中的。但是有一種情況是比較特殊的,就是如果Activity指定了啟動模式是"singleTask",并且啟動的是另外一個應(yīng)用程序中的Activity,這個時候當(dāng)發(fā)現(xiàn)該Activity正好處于一個后臺任務(wù)當(dāng)中的話,就會直接將這整個后臺任務(wù)一起切換到前臺。此時按下返回鍵會優(yōu)先將目前最前臺的任務(wù)(剛剛從后臺切換到最前臺)進(jìn)行回退,下圖比較形象地展示了這種情況:
更多關(guān)于如何在manifest文件中使用啟動模式的講解,可以去參考《第一行代碼——Android》第二章部分的內(nèi)容。
使用Intent flags
除了使用manifest文件之外,你也可以在調(diào)用startActivity()方法的時候,為Intent加入一個flag來改變Activity與任務(wù)的關(guān)聯(lián)方式,下面我們來一一講解一下每種flag的作用:
FLAG_ACTIVITY_NEW_TASK
設(shè)置了這個flag,新啟動Activity就會被放置到一個新的任務(wù)當(dāng)中(與"singleTask"有點(diǎn)類似,但不完全一樣),當(dāng)然這里討論的仍然還是啟動其它程序中的Activity。這個flag的作用通常是模擬一種Launcher的行為,即列出一推可以啟動的東西,但啟動的每一個Activity都是在運(yùn)行在自己獨(dú)立的任務(wù)當(dāng)中的。
FLAG_ACTIVITY_SINGLE_TOP
設(shè)置了這個flag,如果要啟動的Activity在當(dāng)前任務(wù)中已經(jīng)存在了,并且還處于棧頂?shù)奈恢?,那么就不會再次?chuàng)建這個Activity的實(shí)例,而是直接調(diào)用它的onNewIntent()方法。這種flag和在launchMode中指定"singleTop"模式所實(shí)現(xiàn)的效果是一樣的。
FLAG_ACTIVITY_CLEAR_TOP
設(shè)置了這個flag,如果要啟動的Activity在當(dāng)前任務(wù)中已經(jīng)存在了,就不會再次創(chuàng)建這個Activity的實(shí)例,而是會把這個Activity之上的所有Activity全部關(guān)閉掉。比如說,一個任務(wù)當(dāng)中有A、B、C、D四個Activity,然后D調(diào)用了startActivity()方法來啟動B,并將flag指定成FLAG_ACTIVITY_CLEAR_TOP,那么此時C和D就會被關(guān)閉掉,現(xiàn)在返回棧中就只剩下A和B了。
那么此時Activity B會接收到這個啟動它的Intent,你可以決定是讓Activity B調(diào)用onNewIntent()方法(不會創(chuàng)建新的實(shí)例),還是將Activity B銷毀掉并重新創(chuàng)建實(shí)例。如果Activity B沒有在manifest中指定任何啟動模式(也就是"standard"模式),并且Intent中也沒有加入一個FLAG_ACTIVITY_SINGLE_TOP flag,那么此時Activity B就會銷毀掉,然后重新創(chuàng)建實(shí)例。而如果Activity B在manifest中指定了任何一種啟動模式,或者是在Intent中加入了一個FLAG_ACTIVITY_SINGLE_TOP flag,那么就會調(diào)用Activity B的onNewIntent()方法。
FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK結(jié)合在一起使用也會有比較好的效果,比如可以將一個后臺運(yùn)行的任務(wù)切換到前臺,并把目標(biāo)Activity之上的其它Activity全部關(guān)閉掉。這個功能在某些情況下非常有用,比如說從通知欄啟動Activity的時候。
affinity可以用于指定一個Activity更加愿意依附于哪一個任務(wù),在默認(rèn)情況下,同一個應(yīng)用程序中的所有Activity都具有相同的affinity,所以,這些Activity都更加傾向于運(yùn)行在相同的任務(wù)當(dāng)中。當(dāng)然了,你也可以去改變每個Activity的affinity值,通過元素的taskAffinity屬性就可以實(shí)現(xiàn)了。
taskAffinity屬性接收一個字符串參數(shù),你可以指定成任意的值(經(jīng)我測試字符串中至少要包含一個.),但必須不能和應(yīng)用程序的包名相同,因?yàn)橄到y(tǒng)會使用包名來作為默認(rèn)的affinity值。
affinity主要有以下兩種應(yīng)用場景:
當(dāng)調(diào)用startActivity()方法來啟動一個Activity時,默認(rèn)是將它放入到當(dāng)前的任務(wù)當(dāng)中。但是,如果在Intent中加入了一個FLAG_ACTIVITY_NEW_TASK flag的話(或者該Activity在manifest文件中聲明的啟動模式是"singleTask"),系統(tǒng)就會嘗試為這個Activity單獨(dú)創(chuàng)建一個任務(wù)。但是規(guī)則并不是只有這么簡單,系統(tǒng)會去檢測要啟動的這個Activity的affinity和當(dāng)前任務(wù)的affinity是否相同,如果相同的話就會把它放入到現(xiàn)有任務(wù)當(dāng)中,如果不同則會去創(chuàng)建一個新的任務(wù)。而同一個程序中所有Activity的affinity默認(rèn)都是相同的,這也是前面為什么說,同一個應(yīng)用程序中即使聲明成"singleTask",也不會為這個Activity再去創(chuàng)建一個新的任務(wù)了。
當(dāng)把Activity的allowTaskReparenting屬性設(shè)置成true時,Activity就擁有了一個轉(zhuǎn)移所在任務(wù)的能力。具體點(diǎn)來說,就是一個Activity現(xiàn)在是處于某個任務(wù)當(dāng)中的,但是它與另外一個任務(wù)具有相同的affinity值,那么當(dāng)另外這個任務(wù)切換到前臺的時候,該Activity就可以轉(zhuǎn)移到現(xiàn)在的這個任務(wù)當(dāng)中。
那還是舉一個形象點(diǎn)的例子吧,比如有一個天氣預(yù)報程序,它有一個Activity是專門用于顯示天氣信息的,這個Activity和該天氣預(yù)報程序的所有其它Activity具體相同的affinity值,并且還將allowTaskReparenting屬性設(shè)置成true了。這個時候,你自己的應(yīng)用程序通過Intent去啟動了這個用于顯示天氣信息的Activity,那么此時這個Activity應(yīng)該是和你的應(yīng)用程序是在同一個任務(wù)當(dāng)中的。但是當(dāng)把天氣預(yù)報程序切換到前臺的時候,這個Activity又會被轉(zhuǎn)移到天氣預(yù)報程序的任務(wù)當(dāng)中,并顯示出來,因?yàn)樗鼈儞碛邢嗤腶ffinity值,并且將allowTaskReparenting屬性設(shè)置成了true。
如何用戶將任務(wù)切換到后臺之后過了很長一段時間,系統(tǒng)會將這個任務(wù)中除了最底層的那個Activity之外的其它所有Activity全部清除掉。當(dāng)用戶重新回到這個任務(wù)的時候,最底層的那個Activity將得到恢復(fù)。這個是系統(tǒng)默認(rèn)的行為,因?yàn)榧热贿^了這么長的一段時間,用戶很有可能早就忘記了當(dāng)時正在做什么,那么重新回到這個任務(wù)的時候,基本上應(yīng)該是要去做點(diǎn)新的事情了。
當(dāng)然,既然說是默認(rèn)的行為,那就說明我們肯定是有辦法來改變的,在元素中設(shè)置以下幾種屬性就可以改變系統(tǒng)這一默認(rèn)行為:
alwaysRetainTaskState
如果將最底層的那個Activity的這個屬性設(shè)置為true,那么上面所描述的默認(rèn)行為就將不會發(fā)生,任務(wù)中所有的Activity即使過了很長一段時間之后仍然會被繼續(xù)保留。
clearTaskOnLaunch
如果將最底層的那個Activity的這個屬性設(shè)置為true,那么只要用戶離開了當(dāng)前任務(wù),再次返回的時候就會將最底層Activity之上的所有其它Activity全部清除掉。簡單來講,就是一種和alwaysRetainTaskState完全相反的工作模式,它保證每次返回任務(wù)的時候都會是一種初始化狀態(tài),即使用戶僅僅離開了很短的一段時間。
finishOnTaskLaunch
這個屬性和clearTaskOnLaunch是比較類似的,不過它不是作用于整個任務(wù)上的,而是作用于單個Activity上。如果某個Activity將這個屬性設(shè)置成true,那么用戶一旦離開了當(dāng)前任務(wù),再次返回時這個Activity就會被清除掉。