Activity的啟動模式
1. Activity的LaunchMode
我們知道互例,在默認情況下还栓,當我們多次啟動同一個Activity
的時候锦庸,系統(tǒng)會創(chuàng)建多個實例并把它們一一放入任務(wù)棧中块蚌,當我們單擊back
鍵,會發(fā)現(xiàn)這些Activity
會一一回退膘格。任務(wù)棧是一種后進先出
的棧結(jié)構(gòu)峭范,每按一下back
鍵就會有一個Activity
出棧,直到棻窦空為止纱控,當棧中無任何Activity
的時候,系統(tǒng)就會回收這個任務(wù)棧菜秦。
目前有四種啟動模式:standard
甜害,singleTop
,singleTask
和singleInstance
球昨。
(1). standard:標準模式
這是系統(tǒng)的默認模式尔店,每次啟動一個Activity
都會重新創(chuàng)建一個新的實例,不管這個實例是否已經(jīng)存在主慰。被創(chuàng)建的實例的生命周期符合典型情況下Activity
的生命周期嚣州,如上節(jié)所述,它的onCreate
共螺,onStart
该肴,onResume
都會被調(diào)用。這是一種典型的多實例實現(xiàn)藐不,一個任務(wù)棧中可以有多個實例匀哄,每個實例也可以屬于不同的任務(wù)棧。在這種模式下雏蛮,誰啟動了這個Activity
涎嚼,那么這個Activity
就運行在啟動它的那個Activity
所在的棧中。比如ActivityA
啟動了ActivityB
(B
是標準模式)挑秉,那么B
就會進入到A
所在的棧中铸抑。不知你們發(fā)現(xiàn)沒有,當我們用ApplicationContext
去啟動standard
模式的Activity
的時候衷模,會出現(xiàn)錯誤鹊汛。
這是因為standard
模式的Activity
默認會進入啟動它的Activity
所屬的任務(wù)棧中,但是由于非Activity
類型的Context
(如ApplicationContext
)并沒有所謂的任務(wù)棧阱冶,所以這就有問題了刁憋。解決這個問題的方法是為待啟動Activity
指定FLAG_ACTIVITY_NEW_TASK
標記位,這樣啟動的時候就會為它創(chuàng)建一個新的任務(wù)棧木蹬,這個時候待啟動Activity
實際上是以singleTask
模式啟動的至耻。
(2). singleTop:棧頂復(fù)用模式
在這種模式下,如果新Activity
已經(jīng)位于任務(wù)棧的棧頂,那么此Activity
不會被重新創(chuàng)建尘颓,同時它的onNewIntent
方法會被回調(diào)走触,通過此方法的參數(shù)我們可以取出當前請求的信息,需要注意的是疤苹,這個Activity
的onCreate
互广,onStart
不會被系統(tǒng)調(diào)用,因為它并沒有發(fā)生改變卧土。如果新Activity
的實例已存在但不是位于棧頂惫皱,那么新Activity
仍然會重新重建。
(3). singleTask:棧內(nèi)復(fù)用模式
這是一種單實例模式尤莺,在這種模式下旅敷,只要Activity
在一個棧中存在,那么多次啟動此Activity
都不會重新創(chuàng)建實例颤霎,和singleTop
一樣媳谁,系統(tǒng)也會回調(diào)其onNewIntent
。具體一點友酱,當一個具有singleTask
模式的Activity
請求啟動后韩脑,比如ActivityA
,系統(tǒng)首先會尋找是否存在A
想要的任務(wù)棧粹污,如果不存在段多,就重新創(chuàng)建一個任務(wù)棧,然后創(chuàng)建A
的實例后把A
放到棧中壮吩。如果存在A
所需的任務(wù)棧进苍,這時要看A
是否在棧中有實例存在,如果有實例存在鸭叙,那么系統(tǒng)就會把A
調(diào)到棧頂并調(diào)用它的onNewIntent
方法觉啊,如果實例不存在,就創(chuàng)建A
的實例并把A
壓入棧中沈贝。
舉幾個例子:
- 比如目前任務(wù)棧
S1
中的情況為ABC
杠人,這個時候Activity D
以singleTask
模式請求啟動,其所需要的任務(wù)棧為S2
宋下,由于S2
和D
的實例均不存在嗡善,所以系統(tǒng)會先創(chuàng)建任務(wù)棧S2
,然后再創(chuàng)建D
的實例并將其入棧到S2
.
- 另外一種情況学歧,假設(shè)
D
所需的任務(wù)棧為S1
罩引,其他情況如上面例子,那么S1
已經(jīng)存在枝笨,所以系統(tǒng)會直接創(chuàng)建D
的實例并將其入棧到S1
袁铐。
- 如果
D
所需的任務(wù)棧為S1
揭蜒,并且當前任務(wù)棧S1
的情況為ADBC
,根據(jù)棧內(nèi)復(fù)用的原則剔桨,此時D
不會重新創(chuàng)建屉更,系統(tǒng)會把D
切換到棧頂并調(diào)用其onNewIntent
方法,同時由于singleTask
默認具有clearTop
的效果洒缀,會導(dǎo)致棧內(nèi)所有在D
上面的Activity
全部出棧瑰谜,于是最終S1
中的情況為AD
,這一點比較特殊帝洪,在后面還會對此種情況詳細地分析。
(4) singleInstance:單實例模式
這是一種加強的singleTask
模式脚猾,它除了具有singleTask
模式的所有特性外葱峡,還加強了一點,那就是具有此種模式的Activity
只能單獨地位于一個任務(wù)棧中龙助,換句話說砰奕,比如Activity A
是singleInstance
模式,當 A
啟動后提鸟,系統(tǒng)會為它創(chuàng)建一個新的任務(wù)棧军援,然后A
獨自在這個新的任務(wù)棧中,由于棧內(nèi)復(fù)用的特性称勋,后續(xù)的請求均不會創(chuàng)建新的Activity
胸哥,除非這個獨特的任務(wù)棧被系統(tǒng)銷毀了。
這里需要指出一種情況赡鲜,我們假設(shè)目前有2個任務(wù)棧空厌,前臺任務(wù)棧的情況為AB
,而后臺任務(wù)棧的情況為CD
银酬,這里假設(shè)CD
的啟動模式均為singleTask
〕案現(xiàn)在請求啟動D
,那么整個后臺任務(wù)棧都會被切換到前臺揩瞪,這個時候整個后退列表變成了ABCD
。當用戶按Back
鍵的時候,列表中的Activity
會一一出棧寒锚。
如下圖:
如果不是請求啟動
D
而是啟動C
垫蛆,那么情況就不一樣了。如下圖:
另外一問題是嗤攻,在
singleTask
啟動模式中琳拨,多次提到某個Activity
所需的任務(wù)棧,什么是Activity
所需要的任務(wù)棧呢屯曹?這要從一個參數(shù)說起:TaskAffinity
狱庇,可以翻譯為任務(wù)相關(guān)性惊畏。這個參數(shù)標識了一個Activity
所需要的任務(wù)棧的名字,默認情況下密任,所有Activity
所需的任務(wù)棧的名字為應(yīng)用的包名颜启。當然,我們可以為每個Activity
都單獨指定TaskAffinity
屬性浪讳,這個屬性必須不能和包名相同缰盏,否則就相當于沒有指定。TaskAffinity
屬性主要和singleTask
啟動模式或者allowTaskReparenting
屬性配對使用淹遵,在其他情況下沒有意義口猜。另外,任務(wù)棧分為前臺任務(wù)棧和后臺任務(wù)棧透揣,后臺任務(wù)棧中的Activity
位于暫停狀態(tài)济炎,用戶可以通過切換將后臺任務(wù)棧再次調(diào)到前臺。
當TaskAffinity
和singleTask
啟動模式配對使用的時候辐真,它是具有該模式的Activity
的目前任務(wù)棧的名字须尚,待啟動的Activity
會運行在名字和TaskAffinity
相同的任務(wù)棧中。
當TaskAffinity
和allowTaskReparenting
結(jié)合的時候侍咱,這種情況比較復(fù)雜耐床,會產(chǎn)生特殊的效果。當一個應(yīng)用A
啟動了應(yīng)用B
的某個Activity
后楔脯,如果這個Activity
的allowTaskReparenting
屬性為true
的話撩轰,那么當應(yīng)用B
被啟動后,此Activity
會直接從應(yīng)用A
的任務(wù)棧轉(zhuǎn)移到應(yīng)用B
的任務(wù)棧中昧廷。這還是很抽象钧敞,在具體點,比如現(xiàn)在有兩個應(yīng)用A
和B
麸粮,A
啟動了B
的一個ActivityC
溉苛,然后按Home
鍵回到桌面,然后再單擊B
的桌面圖標弄诲,這個時候并不是啟動了B
的主Activity
愚战,而是重新顯示了已經(jīng)被應(yīng)用A
啟動的ActivityC
,或者說齐遵,C
從A
的任務(wù)棧轉(zhuǎn)移到了B
的任務(wù)棧中寂玲。可以這么理解梗摇,由于A
啟動了C
拓哟,這個時候C
只能運行在A
的任務(wù)棧中,但是C
屬于B
應(yīng)用伶授,正常情況下断序,它的TaskAffinity
肯定不可能和A
的任務(wù)棧相同(因為包名不同)流纹,所以,當B
被啟動后违诗,B
會創(chuàng)建自己的任務(wù)棧漱凝,這個時候系統(tǒng)發(fā)現(xiàn)C
原本想要的任務(wù)棧已經(jīng)被創(chuàng)建了,所以就把C
從A
的任務(wù)棧中轉(zhuǎn)移過來了诸迟。
如何給Activity
指定啟動模式呢茸炒?有兩種方法,第一種是通過AndroidMenifest
為Activity
指定啟動模式阵苇。
<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è)置標志位來為Activity
指定啟動模式:
Intent intent = new Intent();
intent.setClass(MainActivity.class,SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
這兩種方式都可以為Activity
指定啟動模式壁公,但是兩者還是有區(qū)別的。首先绅项,優(yōu)先級上紊册,第二種方式的優(yōu)先級要高于第一種,當兩種同時存在時趁怔,以第二種方式為準湿硝;其次薪前,上述兩種方式在限定范圍上有所不同润努,比如,第一種方式無法直接為Activity
設(shè)定FLAG_ACTIVITY_CLEAR_TASK
標識示括,而第二種方式無法為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"
惑折,注意這個taskAffinity
屬性的值為字符串,且中間必須含有包名分隔符"."
枯跑。然后做如下操作惨驶,在MainActivity
中單擊按鈕啟動SecondActivity
,在SecondActivity
中單擊按鈕啟動ThirdActivity
敛助,在ThirdActivity
中單擊按鈕又啟動MainActivity
粗卜,最后再在MainActivity
中單擊按鈕啟動SecondActivity
,現(xiàn)在按2次back
鍵纳击,然后看到的是哪個Activity续扔?
答案是回到桌面攻臀。
首先,從理論上分析這個問題测砂,先假設(shè)MainActivity
為A
茵烈,SecondActivity
為B
,ThirdActivity
為C
砌些。我們知道A
為standard
模式呜投,按照規(guī)定,A
的taskAffinity
值繼承自Application
的taskAffinity
存璃,而Application
的默認taskAffinity
為包名仑荐,所以A
的taskAffinity
為包名。由于我們在XML
中為B
和C
指定了taskAffinity
和啟動模式纵东,所以B
和C
是singleTask
模式且有相同的taskAffinity
值“com.ryg.task1”
粘招。A
啟動B
的時候,按照singleTask
的規(guī)則偎球,這個時候需要為B
重新創(chuàng)建一個任務(wù)棧"com.ryg.task1"
洒扎。B
再啟動C
,按照singleTask
的規(guī)則衰絮,由于C
所需的任務(wù)棧(和B
為同一任務(wù)棧)已經(jīng)被B
創(chuàng)建袍冷,所以無需在創(chuàng)建新的任務(wù)棧,這個時候系統(tǒng)只是創(chuàng)建C
的實例后將C
入棧了猫牡。接著C
在啟動A
胡诗,A
是standard
模式,所以系統(tǒng)會為它創(chuàng)建一個新的實例并將它加到啟動它的那個Activity
的任務(wù)棧淌友,由于是C
啟動了A
煌恢,所以A
會進入C
的任務(wù)棧中并位于棧頂。這個時候已經(jīng)有兩個任務(wù)棧了震庭,一個是名字為包名的任務(wù)棧瑰抵,里面只有A
,另一個是名字為"com.ryg.task1"
的任務(wù)棧器联,里面的Activity
為BCA
二汛。接下來,A
再啟動B
主籍,由于B
是singleTask
习贫,B
需要回到任務(wù)棧的棧頂,由于棧的工作模式為“后進新出”
千元,B
想要回到棧頂苫昌,只能是CA
出棧。所以幸海,到這里就很好理解了祟身,如果再按back
鍵奥务,B
就出棧了,B
所在的任務(wù)棧已經(jīng)不存在了袜硫,這個時候只能是回到后臺任務(wù)棧氯葬,并把A
顯示出來。注意這個A
是后臺任務(wù)棧的A
婉陷,不是"com.ryg.task1"
任務(wù)棧的A
帚称,接著在繼續(xù)back
,就回到桌面了秽澳。分析到這里闯睹,我們得出一條結(jié)論,"singleTask"
模式的Activity
切換到棧頂會導(dǎo)致在它之上的棧內(nèi)的Activity
出棧担神。
Activity的Flags
Activity
的Flag
有很多楼吃,這里主要分析一些比較常用的標記位。標記位的作用很多妄讯,有的標記位可以設(shè)定Activity
的啟動模式孩锡,比如FLAG_ACTIVITY_NEW_TASK
和FLAG_ACTIVITY_SINGLE_TOP
等,還有的標記位可以影響Activity
的運行狀態(tài)亥贸,比如FLAG_ACTIVITY_CLEAR_TOP
和FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
等躬窜。
大部分情況下,我們不需要為Activity
指定標記位砌函,因此斩披,對于標記位理解即可溜族。在使用標記位的時候讹俊,要注意有些標記位是系統(tǒng)內(nèi)部使用的,應(yīng)用程序不需要去手動設(shè)置這些標記位以防止出現(xiàn)問題煌抒。
FLAG_ACTIVITY_NEW_TASK
這個標記位的作用是為Activity
指定singleTask
啟動模式仍劈,其效果和在XML
中指定該啟動模式相同。
FLAG_ACTIVITY_SINGLE_TOP
這個標記位的作用是為Activity
指定singleTop
啟動模式寡壮,其效果和在XML
中指定該啟動模式相同贩疙。
FLAG_ACTIVITY_CLEAR_TOP
具有此標記位的Activity
,當它啟動時况既,在同一個任務(wù)棧中所有位于它上面的Activity
都要出棧这溅。這個標記位一般會和singleTask
啟動模式一起出現(xiàn),在這種情況下棒仍,被啟動Activity
的實例如果已經(jīng)存在悲靴,那么系統(tǒng)就會調(diào)用它的onNewIntent
。如果被啟動的Activity
采用standard
模式啟動莫其,那么它連同它之上的Activity
都要出棧癞尚,系統(tǒng)會創(chuàng)建新的Activity
實例并放入棧頂耸三。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有這個標記位的Activity
不會出現(xiàn)在歷史Activity
的列表中,當某些情況下我們不希望用戶通過歷史列表回到我們的Activity
的時候這個標記比較有用浇揩,它等同于在XML
中指定Activity
的屬性android:excludeFromRecents="true"
仪壮。