Activity的啟動(dòng)模式
1. Activity的LaunchMode
我們知道,在默認(rèn)情況下,當(dāng)我們多次啟動(dòng)同一個(gè)Activity的時(shí)候,系統(tǒng)會(huì)創(chuàng)建多個(gè)實(shí)例并把它們一一放入任務(wù)棧中,當(dāng)我們單擊back鍵,會(huì)發(fā)現(xiàn)這些Activity會(huì)一一回退。任務(wù)棧是一種后進(jìn)先出的棧結(jié)構(gòu),每按一下back鍵就會(huì)有一個(gè)Activity出棧,直到棧空為止,當(dāng)棧中無任何Activity的時(shí)候,系統(tǒng)就會(huì)回收這個(gè)任務(wù)棧。
目前有四種啟動(dòng)模式:standard,singleTop,singleTask和singleInstance。
(1). standard:標(biāo)準(zhǔn)模式
這是系統(tǒng)的默認(rèn)模式,每次啟動(dòng)一個(gè)Activity都會(huì)重新創(chuàng)建一個(gè)新的實(shí)例,不管這個(gè)實(shí)例是否已經(jīng)存在。被創(chuàng)建的實(shí)例的生命周期符合典型情況下Activity的生命周期,如上節(jié)所述,它的onCreate,onStart,onResume都會(huì)被調(diào)用。這是一種典型的多實(shí)例實(shí)現(xiàn),一個(gè)任務(wù)棧中可以有多個(gè)實(shí)例,每個(gè)實(shí)例也可以屬于不同的任務(wù)棧。在這種模式下,誰啟動(dòng)了這個(gè)Activity,那么這個(gè)Activity就運(yùn)行在啟動(dòng)它的那個(gè)Activity所在的棧中。比如ActivityA啟動(dòng)了ActivityB(B是標(biāo)準(zhǔn)模式),那么B就會(huì)進(jìn)入到A所在的棧中。不知你們發(fā)現(xiàn)沒有,當(dāng)我們用ApplicationContext去啟動(dòng)standard模式的Activity的時(shí)候,會(huì)出現(xiàn)錯(cuò)誤。
這是因?yàn)?code>standard模式的Activity默認(rèn)會(huì)進(jìn)入啟動(dòng)它的Activity所屬的任務(wù)棧中,但是由于非Activity類型的Context(如ApplicationContext)并沒有所謂的任務(wù)棧,所以這就有問題了。解決這個(gè)問題的方法是為待啟動(dòng)Activity指定FLAG_ACTIVITY_NEW_TASK標(biāo)記位,這樣啟動(dòng)的時(shí)候就會(huì)為它創(chuàng)建一個(gè)新的任務(wù)棧,這個(gè)時(shí)候待啟動(dòng)Activity實(shí)際上是以singleTask模式啟動(dòng)的。
(2). singleTop:棧頂復(fù)用模式
在這種模式下,如果新Activity已經(jīng)位于任務(wù)棧的棧頂,那么此Activity不會(huì)被重新創(chuàng)建,同時(shí)它的onNewIntent方法會(huì)被回調(diào),通過此方法的參數(shù)我們可以取出當(dāng)前請求的信息,需要注意的是,這個(gè)Activity的onCreate,onStart不會(huì)被系統(tǒng)調(diào)用,因?yàn)樗]有發(fā)生改變。如果新Activity的實(shí)例已存在但不是位于棧頂,那么新Activity仍然會(huì)重新重建。
(3). singleTask:棧內(nèi)復(fù)用模式
這是一種單實(shí)例模式,在這種模式下,只要Activity在一個(gè)棧中存在,那么多次啟動(dòng)此Activity都不會(huì)重新創(chuàng)建實(shí)例,和singleTop一樣,系統(tǒng)也會(huì)回調(diào)其onNewIntent。具體一點(diǎn),當(dāng)一個(gè)具有singleTask模式的Activity請求啟動(dòng)后,比如ActivityA,系統(tǒng)首先會(huì)尋找是否存在A想要的任務(wù)棧,如果不存在,就重新創(chuàng)建一個(gè)任務(wù)棧,然后創(chuàng)建A的實(shí)例后把A放到棧中。如果存在A所需的任務(wù)棧,這時(shí)要看A是否在棧中有實(shí)例存在,如果有實(shí)例存在,那么系統(tǒng)就會(huì)把A調(diào)到棧頂并調(diào)用它的onNewIntent方法,如果實(shí)例不存在,就創(chuàng)建A的實(shí)例并把A壓入棧中。
舉幾個(gè)例子:
- 比如目前任務(wù)棧
S1中的情況為ABC,這個(gè)時(shí)候Activity D以singleTask模式請求啟動(dòng),其所需要的任務(wù)棧為S2,由于S2和D的實(shí)例均不存在,所以系統(tǒng)會(huì)先創(chuàng)建任務(wù)棧S2,然后再創(chuàng)建D的實(shí)例并將其入棧到S2.
- 另外一種情況,假設(shè)
D所需的任務(wù)棧為S1,其他情況如上面例子,那么S1已經(jīng)存在,所以系統(tǒng)會(huì)直接創(chuàng)建D的實(shí)例并將其入棧到S1。
- 如果
D所需的任務(wù)棧為S1,并且當(dāng)前任務(wù)棧S1的情況為ADBC,根據(jù)棧內(nèi)復(fù)用的原則,此時(shí)D不會(huì)重新創(chuàng)建,系統(tǒng)會(huì)把D切換到棧頂并調(diào)用其onNewIntent方法,同時(shí)由于singleTask默認(rèn)具有clearTop的效果,會(huì)導(dǎo)致棧內(nèi)所有在D上面的Activity全部出棧,于是最終S1中的情況為AD,這一點(diǎn)比較特殊,在后面還會(huì)對此種情況詳細(xì)地分析。
(4) singleInstance:單實(shí)例模式
這是一種加強(qiáng)的singleTask模式,它除了具有singleTask模式的所有特性外,還加強(qiáng)了一點(diǎn),那就是具有此種模式的Activity只能單獨(dú)地位于一個(gè)任務(wù)棧中,換句話說,比如Activity A 是singleInstance模式,當(dāng) A 啟動(dòng)后,系統(tǒng)會(huì)為它創(chuàng)建一個(gè)新的任務(wù)棧,然后A獨(dú)自在這個(gè)新的任務(wù)棧中,由于棧內(nèi)復(fù)用的特性,后續(xù)的請求均不會(huì)創(chuàng)建新的Activity,除非這個(gè)獨(dú)特的任務(wù)棧被系統(tǒng)銷毀了。
這里需要指出一種情況,我們假設(shè)目前有2個(gè)任務(wù)棧,前臺(tái)任務(wù)棧的情況為AB,而后臺(tái)任務(wù)棧的情況為CD,這里假設(shè)CD的啟動(dòng)模式均為singleTask?,F(xiàn)在請求啟動(dòng)D,那么整個(gè)后臺(tái)任務(wù)棧都會(huì)被切換到前臺(tái),這個(gè)時(shí)候整個(gè)后退列表變成了ABCD。當(dāng)用戶按Back鍵的時(shí)候,列表中的Activity會(huì)一一出棧。
如下圖:
如果不是請求啟動(dòng)
D而是啟動(dòng)C,那么情況就不一樣了。如下圖:
另外一問題是,在
singleTask啟動(dòng)模式中,多次提到某個(gè)Activity所需的任務(wù)棧,什么是Activity所需要的任務(wù)棧呢?這要從一個(gè)參數(shù)說起:TaskAffinity,可以翻譯為任務(wù)相關(guān)性。這個(gè)參數(shù)標(biāo)識(shí)了一個(gè)Activity所需要的任務(wù)棧的名字,默認(rèn)情況下,所有Activity所需的任務(wù)棧的名字為應(yīng)用的包名。當(dāng)然,我們可以為每個(gè)Activity都單獨(dú)指定TaskAffinity屬性,這個(gè)屬性必須不能和包名相同,否則就相當(dāng)于沒有指定。TaskAffinity屬性主要和singleTask啟動(dòng)模式或者allowTaskReparenting屬性配對使用,在其他情況下沒有意義。另外,任務(wù)棧分為前臺(tái)任務(wù)棧和后臺(tái)任務(wù)棧,后臺(tái)任務(wù)棧中的Activity位于暫停狀態(tài),用戶可以通過切換將后臺(tái)任務(wù)棧再次調(diào)到前臺(tái)。
當(dāng)TaskAffinity和singleTask啟動(dòng)模式配對使用的時(shí)候,它是具有該模式的Activity的目前任務(wù)棧的名字,待啟動(dòng)的Activity會(huì)運(yùn)行在名字和TaskAffinity相同的任務(wù)棧中。
當(dāng)TaskAffinity和allowTaskReparenting結(jié)合的時(shí)候,這種情況比較復(fù)雜,會(huì)產(chǎn)生特殊的效果。當(dāng)一個(gè)應(yīng)用A啟動(dòng)了應(yīng)用B的某個(gè)Activity后,如果這個(gè)Activity的allowTaskReparenting屬性為true的話,那么當(dāng)應(yīng)用B被啟動(dòng)后,此Activity會(huì)直接從應(yīng)用A的任務(wù)棧轉(zhuǎn)移到應(yīng)用B的任務(wù)棧中。這還是很抽象,在具體點(diǎn),比如現(xiàn)在有兩個(gè)應(yīng)用A和B,A啟動(dòng)了B的一個(gè)ActivityC,然后按Home鍵回到桌面,然后再單擊B的桌面圖標(biāo),這個(gè)時(shí)候并不是啟動(dòng)了B的主Activity,而是重新顯示了已經(jīng)被應(yīng)用A啟動(dòng)的ActivityC,或者說,C從A的任務(wù)棧轉(zhuǎn)移到了B的任務(wù)棧中。可以這么理解,由于A啟動(dòng)了C,這個(gè)時(shí)候C只能運(yùn)行在A的任務(wù)棧中,但是C屬于B應(yīng)用,正常情況下,它的TaskAffinity肯定不可能和A的任務(wù)棧相同(因?yàn)榘煌?,所以,當(dāng)B被啟動(dòng)后,B會(huì)創(chuàng)建自己的任務(wù)棧,這個(gè)時(shí)候系統(tǒng)發(fā)現(xiàn)C原本想要的任務(wù)棧已經(jīng)被創(chuàng)建了,所以就把C從A的任務(wù)棧中轉(zhuǎn)移過來了。
如何給Activity指定啟動(dòng)模式呢?有兩種方法,第一種是通過AndroidMenifest為Activity指定啟動(dòng)模式。
<activity
android:name=".MainActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
另一種情況是通過在Intent中設(shè)置標(biāo)志位來為Activity指定啟動(dòng)模式:
Intent intent = new Intent();
intent.setClass(MainActivity.class,SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
這兩種方式都可以為Activity指定啟動(dòng)模式,但是兩者還是有區(qū)別的。首先,優(yōu)先級上,第二種方式的優(yōu)先級要高于第一種,當(dāng)兩種同時(shí)存在時(shí),以第二種方式為準(zhǔn);其次,上述兩種方式在限定范圍上有所不同,比如,第一種方式無法直接為Activity設(shè)定FLAG_ACTIVITY_CLEAR_TASK標(biāo)識(shí),而第二種方式無法為Activity指定singleInstance模式。
standard和singleTop都比較好理解,singleInstance由于其特殊性也好理解,但是關(guān)于singleTask有一種情況需要再說明一下。如上圖所示,如果在ActivityB中請求的不是D而是C,那么情況如何呢?答案是任務(wù)棧列表變成了ABC,ActivityD被直接出棧了。
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:taskAffinity="com.ryg.task1"
android:launchMode="singleTask"/>
<activity
android:name=".ThirdActivity"
android:taskAffinity="com.ryg.task1"
android:launchMode="singleTask">
</activity>
我們將SecondActivity和ThirdActivity都設(shè)成singleTask并指定它們的taskAffinity屬性為"com.ryg.task1",注意這個(gè)taskAffinity屬性的值為字符串,且中間必須含有包名分隔符"."。然后做如下操作,在MainActivity中單擊按鈕啟動(dòng)SecondActivity,在SecondActivity中單擊按鈕啟動(dòng)ThirdActivity,在ThirdActivity中單擊按鈕又啟動(dòng)MainActivity,最后再在MainActivity中單擊按鈕啟動(dòng)SecondActivity,現(xiàn)在按2次back鍵,然后看到的是哪個(gè)Activity?答案是回到桌面。
首先,從理論上分析這個(gè)問題,先假設(shè)MainActivity為A,SecondActivity為B,ThirdActivity為C。我們知道A為standard模式,按照規(guī)定,A的taskAffinity值繼承自Application的taskAffinity,而Application的默認(rèn)taskAffinity為包名,所以A的taskAffinity為包名。由于我們在XML中為B和C指定了taskAffinity和啟動(dòng)模式,所以B和C是singleTask模式且有相同的taskAffinity值“com.ryg.task1”。A啟動(dòng)B的時(shí)候,按照singleTask的規(guī)則,這個(gè)時(shí)候需要為B重新創(chuàng)建一個(gè)任務(wù)棧"com.ryg.task1"。B再啟動(dòng)C,按照singleTask的規(guī)則,由于C所需的任務(wù)棧(和B為同一任務(wù)棧)已經(jīng)被B創(chuàng)建,所以無需在創(chuàng)建新的任務(wù)棧,這個(gè)時(shí)候系統(tǒng)只是創(chuàng)建C的實(shí)例后將C入棧了。接著C在啟動(dòng)A,A是standard模式,所以系統(tǒng)會(huì)為它創(chuàng)建一個(gè)新的實(shí)例并將它加到啟動(dòng)它的那個(gè)Activity的任務(wù)棧,由于是C啟動(dòng)了A,所以A會(huì)進(jìn)入C的任務(wù)棧中并位于棧頂。這個(gè)時(shí)候已經(jīng)有兩個(gè)任務(wù)棧了,一個(gè)是名字為包名的任務(wù)棧,里面只有A,另一個(gè)是名字為"com.ryg.task1"的任務(wù)棧,里面的Activity為BCA。接下來,A再啟動(dòng)B,由于B是singleTask,B需要回到任務(wù)棧的棧頂,由于棧的工作模式為“后進(jìn)新出”,B想要回到棧頂,只能是CA出棧。所以,到這里就很好理解了,如果再按back鍵,B就出棧了,B所在的任務(wù)棧已經(jīng)不存在了,這個(gè)時(shí)候只能是回到后臺(tái)任務(wù)棧,并把A顯示出來。注意這個(gè)A是后臺(tái)任務(wù)棧的A,不是"com.ryg.task1"任務(wù)棧的A,接著在繼續(xù)back,就回到桌面了。分析到這里,我們得出一條結(jié)論,"singleTask"模式的Activity切換到棧頂會(huì)導(dǎo)致在它之上的棧內(nèi)的Activity出棧。
Activity的Flags
Activity的Flag有很多,這里主要分析一些比較常用的標(biāo)記位。標(biāo)記位的作用很多,有的標(biāo)記位可以設(shè)定Activity的啟動(dòng)模式,比如FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_SINGLE_TOP等,還有的標(biāo)記位可以影響Activity的運(yùn)行狀態(tài),比如FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS等。
大部分情況下,我們不需要為Activity指定標(biāo)記位,因此,對于標(biāo)記位理解即可。在使用標(biāo)記位的時(shí)候,要注意有些標(biāo)記位是系統(tǒng)內(nèi)部使用的,應(yīng)用程序不需要去手動(dòng)設(shè)置這些標(biāo)記位以防止出現(xiàn)問題。
FLAG_ACTIVITY_NEW_TASK
這個(gè)標(biāo)記位的作用是為Activity指定singleTask啟動(dòng)模式,其效果和在XML中指定該啟動(dòng)模式相同。
FLAG_ACTIVITY_SINGLE_TOP
這個(gè)標(biāo)記位的作用是為Activity指定singleTop啟動(dòng)模式,其效果和在XML中指定該啟動(dòng)模式相同。
FLAG_ACTIVITY_CLEAR_TOP
具有此標(biāo)記位的Activity,當(dāng)它啟動(dòng)時(shí),在同一個(gè)任務(wù)棧中所有位于它上面的Activity都要出棧。這個(gè)標(biāo)記位一般會(huì)和singleTask啟動(dòng)模式一起出現(xiàn),在這種情況下,被啟動(dòng)Activity的實(shí)例如果已經(jīng)存在,那么系統(tǒng)就會(huì)調(diào)用它的onNewIntent。如果被啟動(dòng)的Activity采用standard模式啟動(dòng),那么它連同它之上的Activity都要出棧,系統(tǒng)會(huì)創(chuàng)建新的Activity實(shí)例并放入棧頂。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有這個(gè)標(biāo)記位的Activity不會(huì)出現(xiàn)在歷史Activity的列表中,當(dāng)某些情況下我們不希望用戶通過歷史列表回到我們的Activity的時(shí)候這個(gè)標(biāo)記比較有用,它等同于在XML中指定Activity的屬性android:excludeFromRecents="true"。