1.2 Activity的啟動(dòng)模式
上一節(jié)介紹了Activity在標(biāo)準(zhǔn)情況下和異常情況下的生命周期麸塞,我們對(duì)Activity的生命周期應(yīng)該有了深入的了解。除了Activity的生命周期外者娱,Activity的啟動(dòng)模式也是一個(gè)難點(diǎn)涌韩,原因是形形色色的啟動(dòng)模式和標(biāo)志位實(shí)在是太容易被混淆了肮帐,但是Activity作為四大組件之首爬早,它的的確確非常重要哼丈,有時(shí)候?yàn)榱藵M足項(xiàng)目的特殊需求,就必須使用Activity的啟動(dòng)模式筛严,所以我們必須要搞清楚它的啟動(dòng)模式和標(biāo)志位醉旦,本節(jié)將會(huì)一一介紹。
1.2.1 Activity的LaunchMode
首先說一下Activity為什么需要啟動(dòng)模式桨啃。我們知道车胡,在默認(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)羹饰,這個(gè)比較好理解,每按一下back鍵就會(huì)有一個(gè)Activity出棧碳却,直到棧空為止笑旺,當(dāng)棧中無任何Activity的時(shí)候昼浦,系統(tǒng)就會(huì)回收這個(gè)任務(wù)棧。關(guān)于任務(wù)棧的系統(tǒng)工作原理筒主,這里暫時(shí)不做說明关噪,在后續(xù)章節(jié)會(huì)專門介紹任務(wù)棧。知道了Activity的默認(rèn)啟動(dòng)模式以后乌妙,我們可能就會(huì)發(fā)現(xiàn)一個(gè)問題:多次啟動(dòng)同一個(gè)Activity使兔,系統(tǒng)重復(fù)創(chuàng)建多個(gè)實(shí)例,這樣不是很傻嗎藤韵?這樣的確有點(diǎn)傻虐沥,Android在設(shè)計(jì)的時(shí)候不可能不考慮到這個(gè)問題,所以它提供了啟動(dòng)模式來修改系統(tǒng)的默認(rèn)行為。目前有四種啟動(dòng)模式:standard欲险、singleTop镐依、singleTask和singleInstance,下面先介紹各種啟動(dòng)模式的含義:
(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í)例實(shí)現(xiàn)鞋真,一個(gè)任務(wù)棧中可以有多個(gè)實(shí)例崇堰,每個(gè)實(shí)例也可以屬于不同的任務(wù)棧。在這種模式下涩咖,誰啟動(dòng)了這個(gè)Activity海诲,那么這個(gè)Activity就運(yùn)行在啟動(dòng)它的那個(gè)Activity所在的棧中。比如Activity A 啟動(dòng)了Activity B(B是標(biāo)準(zhǔn)模式)檩互,那么B就會(huì)進(jìn)入到A所在的棧中特幔。不知道讀者是否注意到,當(dāng)我們用ApplicationContext去啟動(dòng)standrd模式的Activity的時(shí)候會(huì)報(bào)錯(cuò)闸昨,錯(cuò)誤如下:
E/AndroidRuntime(674): android.util.AndroidRuntimeException:
Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.
Is this really what you want?
相信這句話讀者一定不陌生蚯斯,這時(shí)因?yàn)閟tandard模式的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)的横辆,讀者可以仔細(xì)體會(huì)。
(2)singleTop: 棧頂復(fù)用模式茄猫。在這種模式下狈蚤,如果新Activity已經(jīng)位于任務(wù)棧的棧頂,那么此Activity不會(huì)被重新創(chuàng)建划纽,同時(shí)它的onNewIntent方法會(huì)被回調(diào)脆侮,通過此方法的參數(shù)我們可以取出當(dāng)前請(qǐng)求的信息。需要注意的是勇劣,這個(gè)Activity的onCreate靖避、onStart不會(huì)被系統(tǒng)調(diào)用,因?yàn)樗]有發(fā)生改變。如果新的Activity的實(shí)例已存在但不是位于棧頂筋蓖,那么新Activity仍然會(huì)被重新創(chuàng)建卸耘。舉個(gè)例子,假設(shè)目前棧內(nèi)的情況為ABCD粘咖,其中ABCD為四個(gè)Activity蚣抗,A位于棧底,D位于棧頂瓮下,這個(gè)時(shí)候假設(shè)再次啟動(dòng)D翰铡,如果D的啟動(dòng)模式為singleTop,那么棧內(nèi)的情況仍然為ABCD讽坏;如果D的啟動(dòng)模式為standrd锭魔,那么由于D被重新創(chuàng)建,導(dǎo)致棧內(nèi)的情況就變?yōu)锳BCDD路呜。
(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請(qǐng)求啟動(dòng)后轧葛,比如Activity A搂抒,系統(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í)例實(shí)例不存在,就創(chuàng)建A的實(shí)例并把A壓入棧中档插。舉幾個(gè)例子:
- 比如目前任務(wù)棧S1中的情況為ABC慢蜓,這個(gè)時(shí)候Activity D以singleTask模式請(qǐng)求啟動(dòng),其所需要的任務(wù)棧為S2郭膛,由于S2和D的實(shí)例均不存在晨抡,所以系統(tǒng)會(huì)先創(chuàng)建任務(wù)棧S2,然后再創(chuàng)建D的實(shí)例并將其入棧到S2。
- 另外一種情況耘柱,假設(shè)D所需的任務(wù)棧為S1如捅,其他情況如上面例子1所示,那么由于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ì)對(duì)此種情況詳細(xì)地分析。
通過上述3個(gè)例子挎挖,讀者應(yīng)該能比較清晰地理解singleTask的含義了这敬。
(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ù)的請(qǐng)求均不會(huì)創(chuàng)建新的Activity尸闸,除非這個(gè)獨(dú)特的任務(wù)棧被系統(tǒng)銷毀了。
上面介紹了幾種啟動(dòng)模式孕锄,這里需要指出一種情況吮廉,我們假設(shè)目前有2個(gè)任務(wù)棧,前臺(tái)任務(wù)棧的情況為AB畸肆,而后臺(tái)任務(wù)棧的情況為CD宦芦,這里假設(shè)CD的啟動(dòng)模式均為singleTask。現(xiàn)在請(qǐng)求啟動(dòng)D轴脐,那么整個(gè)后臺(tái)任務(wù)棧都會(huì)被切換到前臺(tái)调卑,這個(gè)時(shí)候整個(gè)后退列表變成了ABCD抡砂。當(dāng)用戶按back鍵的時(shí)候,列表中的Activity會(huì)一一出棧恬涧,如圖1-7所示注益。如果不是請(qǐng)求啟動(dòng)D而是啟動(dòng)C,那么情況就不一樣溯捆,請(qǐng)看圖1-8丑搔,具體原因在本節(jié)后面會(huì)再進(jìn)行詳細(xì)分析。
<center>圖1-7 任務(wù)棧示例1</center>
<center>圖1-8 任務(wù)棧示例2</center>
另外一個(gè)問題是现使,在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屬性配對(duì)使用倘要,在其他情況下沒有意義。另外十拣,任務(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)模式配對(duì)使用的時(shí)候泽西,它是具有該模式的Activity的目前任務(wù)棧的名字,待啟動(dòng)的Activity會(huì)運(yùn)行在名字和TaskAffinity相同的任務(wù)棧中缰趋。
當(dāng)TaskAffnity和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)在有2個(gè)應(yīng)用A和B红柱,A啟動(dòng)了B的一個(gè)Activity C,然后按Home鍵回到桌面蓖乘,然后再單擊B的桌面圖標(biāo)锤悄,這個(gè)時(shí)候并不是啟動(dòng)了B的主Activity,而是重新顯示了已經(jīng)被應(yīng)用A啟動(dòng)的Activity C嘉抒,或者說零聚,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)榘煌┖氖健K孕沧。?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)移過來了。這種情況讀者可以寫個(gè)例子測(cè)試一下娱挨,這里就不做示例了余指。
如何給Activity指定啟動(dòng)模式呢?有兩種方法跷坝,第一種是通過AndroidManifest為Activity指定啟動(dòng)模式酵镜,如下所示。
<activity
android:name="com.chenstyle.chapter_1.SecondActivity"
android:configChanges="screenLayout"
android:launchMode="singleTask"
android:label="@string/app_name"
/>
另一種情況是通過在Intent中設(shè)置標(biāo)志位來為Activity指定啟動(dòng)模式探孝,比如:
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
這兩種方式都可以為Activity指定啟動(dòng)模式笋婿,但是二者還是有區(qū)別的。首先顿颅,優(yōu)先級(jí)上缸濒,第二種方式的優(yōu)先級(jí)要高于第一種,當(dāng)兩種同時(shí)存在時(shí)粱腻,以第二種方式為準(zhǔn)庇配;其次,上述兩種方式在限定范圍上有所不同绍些,比如捞慌,第一種方式無法直接為Activity設(shè)定FLAG_ACTIVITY_CLEAR_TOP標(biāo)識(shí),而第二種方式無法為Activity指定singleInstance模式柬批。
關(guān)于Intent中為Activity指定的各種標(biāo)記位啸澡,在下面的小節(jié)中會(huì)繼續(xù)介紹袖订。下面通過一個(gè)例子來體驗(yàn)啟動(dòng)模式的使用效果。還是前面的例子嗅虏,這里我們把MainActivity的啟動(dòng)模式設(shè)為singleTask洛姑,然后重復(fù)啟動(dòng)它,看看是否會(huì)重復(fù)創(chuàng)建皮服,代碼修改如下:
<activity
android:name="com.chenstyle.chaoter_1.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
andorid:launchMode="singleTask"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "onNewIntent, time=" + intent.getLongExtra("time", 0));
}
findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, MainActivity.class)楞艾;
inrent.putExtra("time", System.currentTimeMillis());
startActivity(intent);
}
});
根據(jù)上述修改,我們做如下操作龄广,連續(xù)單擊三次按鈕啟動(dòng)3次MainActivity硫眯,算上原本的MainActivity的實(shí)例,正常情況下择同,任務(wù)棧中應(yīng)該有4個(gè)MainActivity的實(shí)例两入,但是我們?yōu)槠渲贫藄ingleTask模式,現(xiàn)在來看一下到底有何不同奠衔。
執(zhí)行adb shell dumpsys activity命令:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Main stack:
TaskRecord{41350dc8 #9 A com.chenstyle.chapter_1}
Intent {cmp=com.chenstyle.chapter_1/.MainActivity (has extras)}
Hist #1: ActivityRecord{412cc188 com.chenstyle.chapter_1/.MainActivity}
Intent {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x0 cmp=com.chenstyle.chapter_1/.MainActivity bnds=[160,235][240,335] }
ProcessRecord{411e6898 634:com.chenstyle.chapter_1/10052}
TaskRecord{4125abc8 #2 A com.android.launcher}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000000 m.android.launcher/com.android.launcher2.Launcher }
Hist #0: ActivityRecord{412381f8 com.android.launcher/com.android.launcher2.Launcher}
Intent {act=android.intent.action.MAIN cat=[android.intent,category.HOME] flg=0x1000 p=com.android.launcher/com.android.launcher2.Launcher }
ProcessRecord{411d24c8 214:com.android.launcher/10013}
Running activities (most recent first):
TaskRecord{41350dc8 #9 A com.chenstyle/chapter_1}
Run #1: ActivityRecord{412cc188 com.chenstyle.chapter_1/.MainActivity}
TaskRecord{4125abc8 #2 A com.android.launcher}
Run #0: ActivityRecord{412381f8 com.android.launcher/com.android.launcher2.Launcher}
mResumedActivity: ActivityRecord{412cc188 com.chenstyle.chapter_1/.MainActivity}
mFocusedActivity: ActivityRecord{412cc188 com.chenstyle.chapter_1/.MainActivity}
Recent tasks:
* Recent #0: TaskRecord{41350dc8 #9 A com.chenstyle.chapter_1}
* Recent #1: TackRecord{4125abc8 #2 A com.android.launcher}
* Recent #2: TackRecord{412b60a0 #5 A com.essrongs.android.pop.app.InstallMonitorActivity}
從上面導(dǎo)出的Activity信息可以看出谆刨,盡管啟動(dòng)了4次MainActivity,但是它始終只有一個(gè)實(shí)例在任務(wù)棧中归斤。從圖1-9的log可以看出痊夭,Activity的確沒有重新創(chuàng)建,只是暫停了一下脏里,然后調(diào)用onNewIntent她我,接著調(diào)用onResume就又繼續(xù)了。
Level | Tag | Text |
---|---|---|
D | MainActivity | onPause |
D | MainActivity | onNewIntent, time=1422898165307 |
D | MainActivity | onResume |
D | MainActivity | onPause |
D | MainActivity | onNewIntent, time=1422898166173 |
D | MainActivity | onResume |
D | MainActivity | onPause |
D | MainActivity | onNewIntent, time=1422898167429 |
D | MainActivity | onResume |
<center>圖1-9 系統(tǒng)日志</center>
現(xiàn)在我們?nèi)サ魋ingleTask迫横,再來對(duì)比一下番舆,還是同樣的操作,單擊三次按鈕啟動(dòng)MainActivity三次矾踱。
執(zhí)行adb shell dumpsys activity 命令:
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Main stack:
TaskRecord{41325370 #17 A com.chenstyle.chapter_1}
Intent { act=android.intent.action,MAIN cat=[android.intent.category.LAUNCHER] flg=0x100000 p=com.chenstyle.chapter_1/.MainActivity }
Hist #4: ActivityRecord{41236968 com.chenstyle.chapter_1/.MainActivity}
Intent { cmp=com.chenstyle.chapter_1/.MainActivity (has extras)) }
ProcessRecord{411e6898 803:com.chenstyle.chapter_1/10052}
Hist #3: ActivityRecord{411f4b30 com.chenstyle.chapter_1/.MainActivity}
Intent { cmp=com.chenstyle.chapter_1/.MainActivity (has extras)) }
ProcessRecord{411e6898 803:com.chenstyle.chapter_1/10052}
Hist #2: ActivityRecord{411edcb8 com.chenstyle.chapter_1/.MainActivity}
Intent { cmp=com.chenstyle.chapter_1/.MainActivity (has extras)) }
ProcessRecord{411e6898 803:com.chenstyle.chapter_1/10052}
Hist #1: ActivityRecord{411e7588 com.chenstyle.chapter_1/.MainActivity}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x100 cmp=com.chenstyle.chapter_1/.MainActivity}
ProcessRecord{411e6898 803:com.chenstyle.chapter_1/10052}
TaskRecord{4125abc8 #2 A com.android.launcher}
Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000000 cm.android.launcher/com.android.launcher2.Launcher }
Hist #0: ActivityRecord{412381f8 com.android.launcher/com.android.launcher2.Launcher}
Intent {act=android.intent.action.MAIN cat=[android.intent,category.HOME] flg=0x100000 p=com.android.launcher/com.android.launcher2.Launcher }
ProcessRecord{411d24c8 214:com.android.launcher/10013}
Running activities (most recent first):
TaskRecord{41325370 #17 A com.chenstyle.chapter_1}
Run #4: ActivityRecord{41236968 com.chenstyle.chapter_1/.MainActivity}
Run #3: ActivityRecord{411f4b30 com.chenstyle.chapter_1/.MainActivity}
Run #2: ActivityRecord{411edcb8 com.chenstyle.chapter_1/.MainActivity}
Run #1: ActivityRecord{411e7588 com.chenstyle.chapter_1/.MainActivity}
TaskRecord{4125abc8 #2 A com.android.launcher}
Run #0: ActivityRecord{412381f8 com.android.launcher/com.android.launcher2.Launcher}
mResumedActivity: ActivityRecord{41236968 com.chenstyle.chapter_1/.MainActivity}
mFocusedActivity: ActivityRecord{41236968 com.chenstyle.chapter_1/.MainActivity}
Recent tasks:
* Recent #0: TaskRecord{41325370 #9 A com.chenstyle.chapter_1}
* Recent #1: TackRecord{4125abc8 #2 A com.android.launcher}
* Recent #2: TackRecord{412c8d58 #5 A com.essrongs.android.pop.app.InstallMonitorActivity}
上面導(dǎo)出信息很多恨狈,我們可以有選擇地看,比如就看Running activities(most recent first)這一塊呛讲,如下所示禾怠。
Running activities (most recent first):
TaskRecord{41325370 #17 A com.chenstyle.chapter_1}
Run #4: ActivityRecord{41236968 com.chenstyle.chapter_1/.MainActivity}
Run #3: ActivityRecord{411f4b30 com.chenstyle.chapter_1/.MainActivity}
Run #2: ActivityRecord{411edcb8 com.chenstyle.chapter_1/.MainActivity}
Run #1: ActivityRecord{411e7588 com.chenstyle.chapter_1/.MainActivity}
TaskRecord{4125abc8 #2 A com.android.launcher}
Run #0: ActivityRecord{412381f8 com.android.launcher/com.android.launcher2.Launcher}
我們能夠得出目前總共有2個(gè)任務(wù)棧,前臺(tái)任務(wù)棧的taskActivity值為com.chenstyle.chapter_1贝搁,它里面又4個(gè)Activity吗氏,后臺(tái)任務(wù)棧的taskAffinity值為com.android.launcher,它里面有1個(gè)Activity雷逆,這個(gè)Activity就是桌面弦讽。通過這種方式來分析任務(wù)棧就清晰多了。
從上面的導(dǎo)出信息可以看到膀哲,在任務(wù)棧中有4個(gè)MainActivity往产,這也就驗(yàn)證了Activity啟動(dòng)模式的工作方式被碗。
上述四種啟動(dòng)模式,standrd和singleTop都比較好理解捂齐,singleInstance由于其特殊性也好理解蛮放,但是關(guān)于singleTask有一種情況要再說明一下。如圖1-7所示奠宜,如果在Activity B中請(qǐng)求的不是D而是C,那么情況如何呢瞻想?這里可以告訴讀者的是压真,任務(wù)棧列表變成了ABC,是不是很奇怪呢蘑险?Activity D被直接出棧了滴肿。下面我們?cè)儆脤?shí)例驗(yàn)證看看是不是這樣。首先佃迄,還是使用上面的代碼泼差,但是我們做一下修改:
<activity
android:name="com.chenstyle.chapter_1.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
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="com.chenstyle.chapter_1.SecondActivity"
android:configCHanged="screenLayout"
android:label="@string/app_name"
android:taskAffinity="com.chenstyle.task1"
android:launchMode="singleTask" />
<activity
android:name="com.chenstyle.chapter_1.ThirdActivity"
android:configChanges="screenLayout"
andorid:taskAffinity="com.chenstyle.task1"
android:label="@string/app_name"
android:launchMode="singleTask" />
我們將SecondActivity和ThirdActivity都設(shè)成singleTask并指定它們的taskAffinity屬性為“com.chenstyle.task1”,注意這個(gè)taskAffinity屬性的值為字符串呵俏,且中間必須含有包名分隔符“.”堆缘。然后做如下操作,在MainActivity中單擊按鈕啟動(dòng)SecondActivity普碎,在SecondActivity中單擊按鈕啟動(dòng)ThirdActivity吼肥,在ThirdActivity中單擊按鈕又啟動(dòng)MainActivity,最后再在MainActivity中單擊按鈕啟動(dòng)SecondActivity麻车,現(xiàn)在按back鍵缀皱,然后看到的是哪個(gè)Activity?答案是回到桌面动猬。是不是有點(diǎn)摸不到頭腦了啤斗?沒關(guān)系,接下來我們分析這個(gè)問題赁咙。
首先钮莲,從理論上分析這個(gè)問題,先假設(shè)MainActivity為A序目,SecondActivity為B臂痕,ThirdActivity為C。我們知道A為standard模式猿涨,按照規(guī)定握童,A的taskAffinity值繼承自Application的taskAffinity,而Application默認(rèn)為taskAffinity為包名叛赚,所以A的taskAffinity為包名澡绩。由于我們?cè)赬ML中為B和C指定了taskAffinity和啟動(dòng)模式稽揭,所以B和C是singleTask模式且有相同的taskAffinity值“com.chenstyle.task1”。A啟動(dòng)B的時(shí)候肥卡,按照singleTask規(guī)則溪掀,這個(gè)時(shí)候需要為B重新創(chuàng)建一個(gè)任務(wù)棧“com.chenstyle.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是standsrd模式骚勘,所以系統(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.chenstyke.task1”的任務(wù)棧,里面的Activity為BCA定拟。接下來于微,A再啟動(dòng)B,由于B是singleTask青自,B需要回到任務(wù)棧的棧頂株依,由于棧的工作模式為“后進(jìn)先出”,B想要回到棧頂延窜,只能是CA出棧恋腕。所以,到這里就很好理解了逆瑞,如果再按back鍵荠藤,B就出棧了。B所在的任務(wù)棧已經(jīng)不存在了获高,這個(gè)是偶只能是回到后臺(tái)任務(wù)棧并把A顯示出來哈肖。注意這個(gè)A是后臺(tái)任務(wù)棧的A,不是“com.chenstyle.task1”任務(wù)棧的A念秧,接著再繼續(xù)back淤井,就回到桌面了。分析到這里,我們得出一條結(jié)論币狠,singleTask模式的Activity切換到棧頂會(huì)導(dǎo)致在它之上的棧內(nèi)的Activity出棧游两。
接著我們?cè)趯?shí)踐中再次驗(yàn)證這個(gè)問題,還是采用dumpsys命令漩绵。我們省略中間的過程贱案,直接看C啟動(dòng)A的那個(gè)狀態(tài),執(zhí)行 adb shell dumpsys activity 命令止吐,日志如下:
Running activities (mopst recent first):
TaskRecord{4132bd90 #12 A com.chenstyle.task1}
Run #4: ActivityRecord{4133fd18 com.chenstyle.chapter_1/.MainActivity}
Run #3: ActivityRecord{41349c58 com,chenstyle.chapter_1/.ThirdActivity}
Run #2: ActivityRecord{4132bab0 com.chenstyle.chapter_1/.SecondActivity}
TaskRecord{4125a008 #11 A com.chenstyle.chapter_1}
Run #1: ActivityRecord{41328c60 com.chenstyle.chapter_1/.MainActivity}
TaskRecord{41256440 #2 A com.android.launcher}
Run #0: ActivityRecord{41231d30 com.android.launcher/com.android.launcher2.Launcher}
可以清楚地看到有2個(gè)任務(wù)棧宝踪,第一個(gè)(com.chenstyle.chapter_1)只有A,第二個(gè)(com.chenstyle.task1)有BCD碍扔,就如同我們上面分析的那樣肴沫,然后再從A中啟動(dòng)B,再看一下日志:
Running activities (most recent first):
TaskRecord{4132bd90 #12 A com.chenstyle.task1}
Run #2: ActivityRecord{4132bab0 com.chenstyle.chapter_1/.SecondActivity}
TaskRecord{4125a008 #11 A com.chenstyle.chapter_1}
Run #1: ActivityRecord{4132bc60 com.chenstyle.chapter_1/.MainActivity}
TaskRecord{41256440 #2 A com.android.launcher}
Run #0: ActivityRecord{41231d30 com.android.launcher/com.android.launcher2.Launcher
}
可以發(fā)現(xiàn)在任務(wù)棧com.chenstyle.task1中只剩下B了蕴忆,C、A都已經(jīng)出棧了悲幅,這個(gè)時(shí)候再按back鍵套鹅,任務(wù)棧com.chenstyle.chapter_1中的A就顯示出來了,如果再back就回到桌面了汰具。分析到這里卓鹿,相信讀者對(duì)Activity的啟動(dòng)模式已經(jīng)有很深入的理解了。下面介紹Activity中常用的標(biāo)志位留荔。
1.2.2 Activity的Flags
Activity的Flags有很多吟孙,這里主要分析一些比較常用的標(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等巷挥。下面主要介紹幾個(gè)比較常用的標(biāo)記位,剩下的標(biāo)記位讀者可以查看官方文檔去了解验靡,大部分情況下倍宾,我們不需要為Activity指定標(biāo)記位,因此胜嗓,對(duì)于標(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è)模式一般需要和FLAG_ACTIVITY_NEW_TASK配合使用,在這種情況下楞件,被啟動(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í)例并放入棧頂。通過1.2.1節(jié)中的分析可以知道黄伊,singleTask啟動(dòng)模式默認(rèn)就具有此標(biāo)記位的效果泪酱。
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"。