引言
當(dāng)面試官說(shuō)請(qǐng)你介紹一下activity啟動(dòng)模式,大多數(shù)人都能整兩句,什么棧頂復(fù)用啊棧內(nèi)復(fù)用啊甲棍,不過(guò),你確定你真的懂啟動(dòng)模式嗎赶掖?
如果你能回答出下面的問(wèn)題感猛,那么你可以直接退出當(dāng)前界面。
假設(shè)有如下四個(gè)activity:
- A(standard)
- B(singleTop)
- C(singleTask)
- D(singleInstance)
它們的啟動(dòng)順序依次是ABCDABCD,請(qǐng)描述activity棧內(nèi)變化奢赂。
基于交互的分析
例:
1陪白,用戶在主屏幕中點(diǎn)擊應(yīng)用的圖標(biāo)啟動(dòng)應(yīng)用后,彈出了第一Activity界面:A膳灶,并依次打開(kāi)了如下界面 A -> B -> C -> D咱士。
2,此時(shí)按下home鍵返回主屏幕轧钓,然后重新點(diǎn)擊圖標(biāo)啟動(dòng)這個(gè)應(yīng)用序厉,我們會(huì)發(fā)現(xiàn)彈出的界面還是 D 而不是界面 A。
3毕箍,當(dāng)我們連續(xù)點(diǎn)擊返回鍵時(shí)弛房,應(yīng)用中界面會(huì)按照啟動(dòng)順序反向的依次展示,也就是D -> C -> B -> A -> 主屏幕而柑。
通過(guò)這個(gè)例子我們可以知道Android系統(tǒng)會(huì)為應(yīng)用暫時(shí)性的保存一組Activity啟動(dòng)鏈文捶,記錄啟動(dòng)順序,這就引出了第一個(gè)概念:任務(wù)媒咳。
任務(wù)
先說(shuō)下任務(wù)的定義拄轻,Android官方把上述這種為了完成某些工作而鏈?zhǔn)絾?dòng)的一系列Activity合集稱之為 任務(wù)。
我們都知道每個(gè)Activity都是互相獨(dú)立的界面伟葫,正是有了任務(wù)這樣的概念,多個(gè)Activity才能夠關(guān)聯(lián)起來(lái)組成一個(gè)完整的應(yīng)用院促。
任務(wù)可以同時(shí)存在多個(gè)嗎
當(dāng)然可以筏养!
例:平時(shí)我們使用手機(jī)經(jīng)常會(huì)在刷微博和聊微信來(lái)回切換斧抱,每次切換系統(tǒng)都會(huì)為我們保存上一次離開(kāi)的狀態(tài)。
任務(wù)里Activity必須是來(lái)自同一個(gè)應(yīng)用嗎
當(dāng)然不是渐溶!
例:當(dāng)我們?cè)谏缃卉浖O(shè)置用戶頭像時(shí)一般會(huì)有拍照和相冊(cè)兩個(gè)選項(xiàng)辉浦,選擇拍照會(huì)跳轉(zhuǎn)到攝像機(jī)軟件,選擇相冊(cè)會(huì)跳到系統(tǒng)相冊(cè)軟件茎辐。通過(guò)這幾個(gè)軟件之間的共同合作完成了一次任務(wù)宪郊。
任務(wù)中的根Activity
通常情況下,我們都是通過(guò)設(shè)備主屏幕點(diǎn)擊應(yīng)用圖標(biāo)啟動(dòng)應(yīng)用的拖陆,同理設(shè)備主屏幕也是大多數(shù)任務(wù)的起點(diǎn)弛槐,而應(yīng)用中的入口Activity就是這個(gè)任務(wù)的根Activity,根Activity的聲明方式你一定特別熟悉:
<activity
android:name=".HelloActivity"">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
當(dāng)用戶點(diǎn)擊主屏幕應(yīng)用圖標(biāo)打開(kāi)應(yīng)用時(shí)依啰,如果該應(yīng)用最近未曾被使用過(guò)乎串,則會(huì)創(chuàng)建一個(gè)任務(wù),并將該應(yīng)用中的入口Activity作為任務(wù)中的根Activity打開(kāi)速警。反之直接把該應(yīng)用所在的任務(wù)調(diào)出來(lái)置于前臺(tái)即可叹誉。
了解完任務(wù)之后,我們就大概知道了上述幾個(gè)例子中Android系統(tǒng)如何保存Activity使用狀態(tài)的規(guī)則闷旧。
返回棧
任務(wù)呢是一個(gè)特別虛的概念长豁,是為了方便開(kāi)發(fā)者理解才有的它,而系統(tǒng)中真正存儲(chǔ)Activity的是一個(gè)遵循先進(jìn)后出原則的數(shù)據(jù)結(jié)構(gòu):棧忙灼。一般叫它返回棧(任務(wù)棧匠襟,堆棧,其實(shí)叫什么的都有)缀棍。
返回棧是任務(wù)的實(shí)際載體宅此,每個(gè)任務(wù)中所有的Activity都會(huì)按照各自的打開(kāi)順序保存在對(duì)應(yīng)的返回棧中。所以Android系統(tǒng)顯示界面的順序是先找到要顯示界面所在的任務(wù)爬范,然后在對(duì)應(yīng)的返回棧中找到顯示的Activity父腕。
值得一提的是由于返回棧存儲(chǔ)結(jié)構(gòu)的特殊性,外部只能訪問(wèn)到棧頂?shù)腁ctivity青瀑,也就是最后入棧的那個(gè)璧亮。所以一個(gè)Activity想要能顯示在屏幕上那么它必須存在于棧頂位置。
進(jìn)棧與出棧
當(dāng)前 Activity 啟動(dòng)另一個(gè) Activity 時(shí)斥难,新的 Activity 會(huì)被推送到堆棧頂部枝嘶,成為焦點(diǎn)顯示在屏幕上。 前一個(gè) Activity 仍保留在堆棧中哑诊,但是處于停止?fàn)顟B(tài)群扶。
用戶按“返回”按鈕時(shí),當(dāng)前 Activity 會(huì)從堆棧頂部彈出(Activity 被銷毀),而前一個(gè) Activity 恢復(fù)執(zhí)行竞阐。如果用戶繼續(xù)按“返回”缴饭,堆棧中的相應(yīng) Activity 就會(huì)彈出,以顯示前一個(gè) Activity骆莹,直到用戶返回主屏幕為止(或者颗搂,返回任務(wù)開(kāi)始時(shí)正在運(yùn)行的任意 Activity)。 當(dāng)所有 Activity 均從堆棧中移除后幕垦,任務(wù)即不復(fù)存在丢氢。棧也就會(huì)被回收掉。
特殊的任務(wù)
通過(guò)前面的了解先改,我們知道如果要打開(kāi)新的界面需要把Activity實(shí)例放到當(dāng)前任務(wù)對(duì)應(yīng)的返回棧的棧頂疚察。該操作是不管該Activity之前有沒(méi)有實(shí)例化過(guò)或者棧中是否已經(jīng)存在了的。
但是盏道,有些特殊情況下稍浆,我們會(huì)發(fā)現(xiàn)一些“例外”。
例1:當(dāng)來(lái)自多個(gè)不同任務(wù)中的應(yīng)用選擇使用系統(tǒng)瀏覽器訪問(wèn)網(wǎng)頁(yè)的時(shí)候猜嘱,瀏覽器應(yīng)用并不會(huì)在每個(gè)任務(wù)的返回棧中都創(chuàng)建Activity衅枫,而是將所有網(wǎng)頁(yè)以選項(xiàng)卡的形式展示在同一個(gè)界面中。
本例中瀏覽器應(yīng)用的Activity如果已經(jīng)實(shí)例化過(guò)了就不會(huì)重新創(chuàng)建朗伶。
例2:小明在微信中向你分享了一條微博內(nèi)容弦撩,你打開(kāi)后跳轉(zhuǎn)到了微博APP中的該條微博詳情頁(yè),當(dāng)你看完內(nèi)容后按返回鍵退出該界面發(fā)現(xiàn)并不是回到了微信聊天界面论皆,而是來(lái)到了微博主頁(yè)(或上一次在微博中停留的界面)益楼。
本例中微博詳情頁(yè)的Activity雖然是由微信應(yīng)用所在的任務(wù)啟動(dòng),但是沒(méi)有加入到微信應(yīng)用的任務(wù)中点晴,而是加入到了微博的任務(wù)棧中感凤。
管理任務(wù)
很顯然上述兩個(gè)例子在實(shí)際使用中并不少見(jiàn),對(duì)于這種特殊的情況我們需要針對(duì)性的管理任務(wù)粒督,而眾所周知的啟動(dòng)模式僅僅是其中的一種陪竿。
定義啟動(dòng)模式
定義Activity的啟動(dòng)模式其實(shí)就是定義一個(gè)Activity的新實(shí)例如何(是否)與當(dāng)前任務(wù)做關(guān)聯(lián)。以什么樣的方式進(jìn)入到當(dāng)前(或其他)任務(wù)中屠橄。
如果你只說(shuō)Activity的啟動(dòng)模式有四種族跛,其實(shí)是不準(zhǔn)確的,因?yàn)槲覀兛梢酝ㄟ^(guò)兩種方法定義不同的啟動(dòng)模式:
使用AndroidManifest.xml中定義
在AndroidManifest.xml中<activity>標(biāo)簽下使用lauchMode屬性來(lái)指定當(dāng)前這個(gè)activity的啟動(dòng)模式锐墙。
使用Intent標(biāo)志定義
在調(diào)用startActivity(Intent intent)前礁哄,通過(guò)調(diào)用intent.addFlags()或者intent.setFlags()方法為Intent添加一個(gè)標(biāo)志,用于為將要啟動(dòng)的Activity聲明啟動(dòng)模式溪北。
那兩者有什么區(qū)別呢桐绒?
上述兩種方法均可以為activity聲明啟動(dòng)模式夺脾,只是使用情景不同。
如果我們希望某個(gè)activity在任何情況下都會(huì)執(zhí)行一種特殊的啟動(dòng)模式掏膏,我們就可以采用AndroidManifest.xml的方法聲明劳翰。
如果我們希望某個(gè)activity大多數(shù)情況下正常啟動(dòng),而少數(shù)情況下執(zhí)行特殊的啟動(dòng)模式馒疹,我們就可以在需要執(zhí)行特殊啟動(dòng)模式時(shí)在Intent中添加標(biāo)志聲明。
如果一個(gè)activity兩種方式都聲明了的話乙墙,使用Intent標(biāo)志的方式要比AndroidManifest.xml的優(yōu)先級(jí)高颖变。
兩種方式中定義的啟動(dòng)模式有些是不一樣的,Intent標(biāo)志中定義的某些啟動(dòng)模式AndroidManifest.xml中沒(méi)有听想,反之一樣腥刹。
我們常說(shuō)的四種啟動(dòng)模式其實(shí)說(shuō)的是AndroidManifest.xml中定義的。
使用AndroidManifest.xml聲明啟動(dòng)模式
在清單文件中聲明 Activity 時(shí)汉买,您可以使用<activity>
元素的 ][launchMode
屬性指定 Activity 應(yīng)該如何與任務(wù)關(guān)聯(lián)衔峰。
您可以分配給 launchMode
屬性的啟動(dòng)模式共有四種:
standard
singleTop
singleTask
singleInstance
先不用管他們具體的操作是什么,我們首先要知道這四種啟動(dòng)模式可以分為兩大類:
-
standard
和singleTop
該類啟動(dòng)模式的activity可以被多次的實(shí)例化蛙粘,它們的實(shí)例可以放到任何任務(wù)中垫卤,并且可以位于返回棧的任何位置。 -
singleTask
和singleInstance
帶有此類啟動(dòng)模式的activity出牧,它們只能有一個(gè)實(shí)例存在穴肘,且實(shí)例只能存在于單獨(dú)的任務(wù)中。
standard:標(biāo)準(zhǔn)模式
默認(rèn)啟動(dòng)模式舔痕,啟動(dòng)activity時(shí)直接創(chuàng)建新的實(shí)例并壓入啟動(dòng)它的任務(wù)棧頂评抚。
singleTop:棧頂復(fù)用模式
該模式唯一與standard
不同的就是,如果啟動(dòng)singleTop
模式的activity時(shí)發(fā)現(xiàn)當(dāng)前任務(wù)的棧頂已經(jīng)存在著這個(gè)activity的實(shí)例伯复,那么就不會(huì)創(chuàng)建新的實(shí)例慨代,而是調(diào)用該實(shí)例的onNewIntent()
方法。其他的跟標(biāo)準(zhǔn)模式一樣啸如。
singleTask:棧內(nèi)復(fù)用模式
這個(gè)模式有些特殊一點(diǎn)侍匙,我們先按使用情景介紹它,當(dāng)我們將要啟動(dòng)該模式的activity時(shí)组底,系統(tǒng)會(huì)判斷當(dāng)前是否有它想要的任務(wù)棧:
沒(méi)有它要的任務(wù)棧
系統(tǒng)會(huì)新創(chuàng)建一個(gè)任務(wù)丈积,并將該activity實(shí)例化作為該任務(wù)的根activity。
有它要的任務(wù)棧
這時(shí)候系統(tǒng)會(huì)找到該任務(wù)棧债鸡,如果任務(wù)棧里只有它自己則直接調(diào)用該activity實(shí)例的onNewIntent()方法江滨。如果任務(wù)棧中它的上方還存在別的activity,那么這些activity會(huì)被全部彈出棧厌均。
至于什么是“它想要的任務(wù)椈;”,我們會(huì)在下面單獨(dú)分析。
singleInstance:?jiǎn)卫J?/strong>
基本上跟singleTask
相同晶密,會(huì)為activity單獨(dú)創(chuàng)建一個(gè)任務(wù)并能夠復(fù)用擒悬。但是該模式的activity不允許其他activity跟自己存在于同一個(gè)任務(wù)中,由此 activity 啟動(dòng)的任何 activity 均會(huì)被在其他的任務(wù)中打開(kāi)稻艰。
使用Intent標(biāo)志聲明啟動(dòng)模式
此方式可以通過(guò)調(diào)用intent.addFlags(int flags)
或者intent.setFlags(int flags)
方法為Intent添加一個(gè)標(biāo)志懂牧,用于為將要啟動(dòng)的Activity聲明啟動(dòng)模式。
在開(kāi)始介紹前尊勿,先進(jìn)行幾點(diǎn)掃盲科普:
- 一個(gè)Intent可以設(shè)置多個(gè)標(biāo)志僧凤,這就是為啥有
addflags()
和setFlags()
兩個(gè)方法的原因了。 - 為Intent設(shè)置標(biāo)志的參數(shù)都是Intent類的靜態(tài)常量元扔。
- 設(shè)置Intent標(biāo)志不光只有設(shè)置activity啟動(dòng)模式這一個(gè)功能躯保,設(shè)置不同的參數(shù)還有其他功能。
- Intent標(biāo)志中可以對(duì)activity啟動(dòng)模式進(jìn)行操作的標(biāo)志可多了澎语,我們只介紹特別典型的三種途事。
Intent.FLAG_ACTIVITY_SINGLE_TOP
同AndroidManifest.xml
方式中的singleTop
啟動(dòng)模式。
Intent.FLAG_ACTIVITY_NEW_TASK
同AndroidManifest.xml
方式中的singleTask
啟動(dòng)模式擅羞。
Intent.FLAG_ACTIVITY_CLEAR_TOP
如果即將啟動(dòng)的 activity 已經(jīng)存在于當(dāng)前任務(wù)棧中尸变,則會(huì)彈出銷毀它上方的所有 activity,并調(diào)用該activity實(shí)例的onNewIntent()
方法祟滴,而不是啟動(dòng)該 Activity 的新實(shí)例振惰。
跟singleTask
有點(diǎn)像但不一樣,在AndroidManifest.xml
方式中沒(méi)有與此對(duì)應(yīng)的值垄懂。
singleTask
默認(rèn)就包含了FLAG_ACTIVITY_CLEAR_TOP
的功能骑晶。
關(guān)聯(lián)任務(wù)
在分析singleTask
時(shí)有提到過(guò)該模式下啟動(dòng)activity前會(huì)去找“它想要的任務(wù)棧”草慧,那么如何去找呢桶蛔?這就引出了AndroidManifest.xml
中<activity>
標(biāo)簽下的taskAffinity
屬性。
taskAffinity 屬性
taskAffinity 屬性學(xué)名任務(wù)相關(guān)性漫谷,說(shuō)白了其實(shí)就是這個(gè)參數(shù)可以指定當(dāng)前activity所屬任務(wù)棧的名字仔雷,該屬性的值為字符串:
例:android:taskAffinity="com.test.demo.task1"
如果你在<activity>
標(biāo)簽沒(méi)指定這個(gè)屬性,那么它就用<application>
標(biāo)簽的taskAffinity
屬性舔示,如果<application>
標(biāo)簽下也沒(méi)指定碟婆,它就應(yīng)用包名當(dāng)做默認(rèn)值。
taskAffinity 與 singleTask
了解到taskAffinity
屬性后我們?cè)谥匦率崂硪幌?code>singleTask啟動(dòng)模式惕稻。
- 如果我們指定了
taskAffinity
屬性的值竖共,那么就跟之前分析的一樣,創(chuàng)建新任務(wù)等等... - 如果我們未指定
taskAffinity
屬性的值俺祠,新activity就與當(dāng)前任務(wù)的taskAffinity
屬性值一樣公给,所以新activity的實(shí)例會(huì)被放置到當(dāng)前的任務(wù)棧中借帘。
除了singleTask呢?
現(xiàn)在我們知道了taskAffinity
屬性可以強(qiáng)行指定activity所屬的任務(wù)棧淌铐,那么這個(gè)屬性在其他啟動(dòng)模式情況下是什么樣的呢肺然?網(wǎng)上好多人都說(shuō)沒(méi)有效果,我不信就親自測(cè)試了一下得出以下結(jié)論:
剛介紹
SingleInstance
的時(shí)候說(shuō)它跟singleTask
一樣都會(huì)新建一個(gè)任務(wù)腿准,既然singleTask
是根據(jù)taskAffinity
屬性來(lái)決定是否需要新建任務(wù)的际起,那么singleInstance
是不是也需要指定這個(gè)屬性呢?
答案是否释涛!只要啟動(dòng)模式為singleInstance
它一定會(huì)單獨(dú)開(kāi)一個(gè)任務(wù)加叁。SingleTop
模式下指定了taskAffinity
屬性的值后,他就會(huì)神奇的切換到指定的那個(gè)任務(wù)棧中唇撬,除此之外跟原來(lái)一樣。最神奇的就是
Standard
展融,它也同樣受到了taskAffinity
屬性的影響窖认,也會(huì)切換到指定的那個(gè)任務(wù)棧中,但當(dāng)我們多次啟動(dòng)這個(gè)activity時(shí)它不會(huì)再多次的創(chuàng)建實(shí)例告希,而是拉起了之前啟動(dòng)過(guò)的實(shí)例扑浸,更特殊的是,其他三種啟動(dòng)模式在復(fù)用之前實(shí)例時(shí)都會(huì)調(diào)用onNewIntent()
方法燕偶,他卻不會(huì)調(diào)用該方法喝噪。
taskAffinity的其他作用
taskAffinity還有一個(gè)功能就是可以重新定向所屬任務(wù),意思就是這個(gè)activity原來(lái)是屬于任務(wù)A的指么,當(dāng)有一個(gè)跟該activity的taskAffinity屬性值相同的任務(wù)B被創(chuàng)建后酝惧,這個(gè)activity就會(huì)從任務(wù)A中轉(zhuǎn)移到任務(wù)B中。
想要實(shí)現(xiàn)這個(gè)功能我們還需要allowTaskReparenting
屬性的配合:
- 我們?cè)谇鍐挝募薪o
taskAffinity="A"
的activity標(biāo)簽下添加屬性android:allowTaskReparenting=true
伯诬。 - 在
taskAffinity="B"
的任務(wù)下啟動(dòng)這個(gè)activity晚唇,此時(shí)這個(gè)activity存在于任務(wù)B中。 - 當(dāng)
taskAffinity="A"
的任務(wù)被創(chuàng)建或者被置于前臺(tái)盗似,該activity將被轉(zhuǎn)移到其任務(wù)棧中哩陕,位于棧頂位置。
清理任務(wù)
如果用戶長(zhǎng)時(shí)間離開(kāi)任務(wù)赫舒,則系統(tǒng)會(huì)清除所有 Activity 的任務(wù)悍及,根 Activity 除外。 當(dāng)用戶再次返回到任務(wù)時(shí)接癌,僅恢復(fù)根 Activity心赶。系統(tǒng)這樣做的原因是,經(jīng)過(guò)很長(zhǎng)一段時(shí)間后扔涧,用戶可能已經(jīng)放棄之前執(zhí)行的操作园担,返回到任務(wù)是要開(kāi)始執(zhí)行新的操作届谈。
您可以使用下列幾個(gè) Activity 屬性修改此行為:
alwaysRetainTaskState
如果在任務(wù)的根 Activity 中將此屬性設(shè)置為 "true",則不會(huì)發(fā)生剛才所述的默認(rèn)行為弯汰。即使在很長(zhǎng)一段時(shí)間后艰山,任務(wù)仍將所有 Activity 保留在其堆棧中。
clearTaskOnLaunch
如果在任務(wù)的根 Activity 中將此屬性設(shè)置為 "true"咏闪,則每當(dāng)用戶離開(kāi)任務(wù)然后返回時(shí)曙搬,系統(tǒng)都會(huì)將堆棧清除到只剩下根 Activity。 即使只離開(kāi)任務(wù)片刻時(shí)間鸽嫂,用戶也始終會(huì)返回到任務(wù)的初始狀態(tài)纵装。
finishOnTaskLaunch
類似于clearTaskOnLaunch,但是更狠一些据某,當(dāng)用戶離開(kāi)任務(wù)再回來(lái)的時(shí)候橡娄,整個(gè)任務(wù)的activity都會(huì)清除,連根activity也是癣籽,相當(dāng)于第一次啟動(dòng)這個(gè)任務(wù)挽唉。
啟動(dòng)模式的實(shí)際應(yīng)用
個(gè)人覺(jué)得常見(jiàn)的四種啟動(dòng)模式中要屬singleTop
不是很好理解,其他的還好筷狼。
singleTop
singleTop
模式的activity特點(diǎn)就是除了外部可以啟動(dòng)它顯示信息外瓶籽,它也可以用同樣的方式啟動(dòng)自己更新顯示信息,這樣就減少了冗余代碼埂材,降低了維護(hù)成本塑顺。
例:如果讓你設(shè)計(jì)一個(gè)帶有搜索應(yīng)用的APP,主頁(yè)有一個(gè)搜索框俏险,輸入信息點(diǎn)擊搜索按鈕進(jìn)入結(jié)果頁(yè)顯示結(jié)果严拒,為方便用戶使用,結(jié)果頁(yè)也有一個(gè)搜索框寡喝,跟主頁(yè)的搜索框功能一樣糙俗,你會(huì)怎么設(shè)計(jì)?
singleTask
這個(gè)啟動(dòng)模式可以分為兩種情況:
- 未指定taskAffinity
此時(shí)該activity可以當(dāng)應(yīng)用中的某一模塊的入口處
有如下啟動(dòng)流程预鬓,微信主頁(yè) >> 聊天頁(yè) >> 聊天設(shè)置頁(yè) >> 用戶資料頁(yè) >> 聊天頁(yè)巧骚,此時(shí)我們按下返回鍵直接回到了微信主頁(yè)。
- 指定了taskAffinity
如果利用該啟動(dòng)模式新開(kāi)了任務(wù)格二,在用戶的視角里就相當(dāng)開(kāi)了兩個(gè)應(yīng)用(在任務(wù)管理器中會(huì)看到兩個(gè)最近應(yīng)用)劈彪,所以謹(jǐn)慎使用,我能想到的使用情況就是一個(gè)Word應(yīng)用打開(kāi)了兩份文檔顶猜。
singleInstance
這種情況下不管有多少個(gè)任務(wù)啟動(dòng)它沧奴,它都會(huì)作為一個(gè)單獨(dú)任務(wù)存在著,這種模式極其特殊长窄,謹(jǐn)慎使用滔吠。
例:撥號(hào)界面纲菌,鬧鐘界面。