Activity的啟動(dòng)模式

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ì)分析。

圖1-7 任務(wù)棧示例1.png

<center>圖1-7 任務(wù)棧示例1</center>

圖1-8 任務(wù)棧示例2.png

<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"。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拓轻,一起剝皮案震驚了整個(gè)濱河市斯撮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扶叉,老刑警劉巖勿锅,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異枣氧,居然都是意外死亡溢十,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門作瞄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茶宵,“玉大人,你說我怎么就攤上這事宗挥∥谑” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵契耿,是天一觀的道長(zhǎng)瞒大。 經(jīng)常有香客問我,道長(zhǎng)搪桂,這世上最難降的妖魔是什么透敌? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任盯滚,我火速辦了婚禮,結(jié)果婚禮上酗电,老公的妹妹穿的比我還像新娘魄藕。我一直安慰自己,他們只是感情好撵术,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布背率。 她就那樣靜靜地躺著,像睡著了一般嫩与。 火紅的嫁衣襯著肌膚如雪寝姿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天划滋,我揣著相機(jī)與錄音饵筑,去河邊找鬼。 笑死处坪,一個(gè)胖子當(dāng)著我的面吹牛根资,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播同窘,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼嫂冻,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了塞椎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤睛低,失蹤者是張志新(化名)和其女友劉穎案狠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钱雷,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骂铁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了罩抗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拉庵。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖套蒂,靈堂內(nèi)的尸體忽然破棺而出钞支,到底是詐尸還是另有隱情,我是刑警寧澤操刀,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布烁挟,位于F島的核電站,受9級(jí)特大地震影響骨坑,放射性物質(zhì)發(fā)生泄漏撼嗓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望且警。 院中可真熱鬧粉捻,春花似錦、人聲如沸斑芜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽押搪。三九已至树酪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間大州,已是汗流浹背续语。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厦画,地道東北人疮茄。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像根暑,于是被迫代替她去往敵國(guó)和親力试。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容