引言
當(dāng)面試官說請你介紹一下activity啟動(dòng)模式,大多數(shù)人都能整兩句,什么棧頂復(fù)用啊棧內(nèi)復(fù)用啊,不過,你確定你真的懂啟動(dòng)模式嗎?
如果你能回答出下面的問題,那么你可以直接退出當(dāng)前界面。
假設(shè)有如下四個(gè)activity:
- A(standard)
- B(singleTop)
- C(singleTask)
- D(singleInstance)
它們的啟動(dòng)順序依次是ABCDABCD,請描述activity棧內(nèi)變化。
基于交互的分析
例:
1,用戶在主屏幕中點(diǎn)擊應(yīng)用的圖標(biāo)啟動(dòng)應(yīng)用后,彈出了第一Activity界面:A,并依次打開了如下界面 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 -> 主屏幕。
通過這個(gè)例子我們可以知道Android系統(tǒng)會(huì)為應(yīng)用暫時(shí)性的保存一組Activity啟動(dòng)鏈,記錄啟動(dòng)順序,這就引出了第一個(gè)概念:任務(wù)。
任務(wù)
先說下任務(wù)的定義,Android官方把上述這種為了完成某些工作而鏈?zhǔn)絾?dòng)的一系列Activity合集稱之為 任務(wù)。
我們都知道每個(gè)Activity都是互相獨(dú)立的界面,正是有了任務(wù)這樣的概念,多個(gè)Activity才能夠關(guān)聯(lián)起來組成一個(gè)完整的應(yīng)用。
任務(wù)可以同時(shí)存在多個(gè)嗎
當(dāng)然可以!
例:平時(shí)我們使用手機(jī)經(jīng)常會(huì)在刷微博和聊微信來回切換,每次切換系統(tǒng)都會(huì)為我們保存上一次離開的狀態(tài)。
任務(wù)里Activity必須是來自同一個(gè)應(yīng)用嗎
當(dāng)然不是!
例:當(dāng)我們在社交軟件設(shè)置用戶頭像時(shí)一般會(huì)有拍照和相冊兩個(gè)選項(xiàng),選擇拍照會(huì)跳轉(zhuǎn)到攝像機(jī)軟件,選擇相冊會(huì)跳到系統(tǒng)相冊軟件。通過這幾個(gè)軟件之間的共同合作完成了一次任務(wù)。
任務(wù)中的根Activity
通常情況下,我們都是通過設(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)打開應(yīng)用時(shí),如果該應(yīng)用最近未曾被使用過,則會(huì)創(chuàng)建一個(gè)任務(wù),并將該應(yīng)用中的入口Activity作為任務(wù)中的根Activity打開。反之直接把該應(yīng)用所在的任務(wù)調(diào)出來置于前臺(tái)即可。
了解完任務(wù)之后,我們就大概知道了上述幾個(gè)例子中Android系統(tǒng)如何保存Activity使用狀態(tài)的規(guī)則。
返回棧
任務(wù)呢是一個(gè)特別虛的概念,是為了方便開發(fā)者理解才有的它,而系統(tǒng)中真正存儲(chǔ)Activity的是一個(gè)遵循先進(jìn)后出原則的數(shù)據(jù)結(jié)構(gòu):棧。一般叫它返回棧(任務(wù)棧,堆棧,其實(shí)叫什么的都有)。
返回棧是任務(wù)的實(shí)際載體,每個(gè)任務(wù)中所有的Activity都會(huì)按照各自的打開順序保存在對應(yīng)的返回棧中。所以Android系統(tǒng)顯示界面的順序是先找到要顯示界面所在的任務(wù),然后在對應(yīng)的返回棧中找到顯示的Activity。
值得一提的是由于返回棧存儲(chǔ)結(jié)構(gòu)的特殊性,外部只能訪問到棧頂?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ù)開始時(shí)正在運(yùn)行的任意 Activity)。 當(dāng)所有 Activity 均從堆棧中移除后,任務(wù)即不復(fù)存在。棧也就會(huì)被回收掉。
特殊的任務(wù)
通過前面的了解,我們知道如果要打開新的界面需要把Activity實(shí)例放到當(dāng)前任務(wù)對應(yīng)的返回棧的棧頂。該操作是不管該Activity之前有沒有實(shí)例化過或者棧中是否已經(jīng)存在了的。
但是,有些特殊情況下,我們會(huì)發(fā)現(xiàn)一些“例外”。
例1:當(dāng)來自多個(gè)不同任務(wù)中的應(yīng)用選擇使用系統(tǒng)瀏覽器訪問網(wǎng)頁的時(shí)候,瀏覽器應(yīng)用并不會(huì)在每個(gè)任務(wù)的返回棧中都創(chuàng)建Activity,而是將所有網(wǎng)頁以選項(xiàng)卡的形式展示在同一個(gè)界面中。
本例中瀏覽器應(yīng)用的Activity如果已經(jīng)實(shí)例化過了就不會(huì)重新創(chuàng)建。
例2:小明在微信中向你分享了一條微博內(nèi)容,你打開后跳轉(zhuǎn)到了微博APP中的該條微博詳情頁,當(dāng)你看完內(nèi)容后按返回鍵退出該界面發(fā)現(xiàn)并不是回到了微信聊天界面,而是來到了微博主頁(或上一次在微博中停留的界面)。
本例中微博詳情頁的Activity雖然是由微信應(yīng)用所在的任務(wù)啟動(dòng),但是沒有加入到微信應(yīng)用的任務(wù)中,而是加入到了微博的任務(wù)棧中。
管理任務(wù)
很顯然上述兩個(gè)例子在實(shí)際使用中并不少見,對于這種特殊的情況我們需要針對性的管理任務(wù),而眾所周知的啟動(dòng)模式僅僅是其中的一種。
定義啟動(dòng)模式
定義Activity的啟動(dòng)模式其實(shí)就是定義一個(gè)Activity的新實(shí)例如何(是否)與當(dāng)前任務(wù)做關(guān)聯(lián)。以什么樣的方式進(jìn)入到當(dāng)前(或其他)任務(wù)中。
如果你只說Activity的啟動(dòng)模式有四種,其實(shí)是不準(zhǔn)確的,因?yàn)槲覀兛梢酝ㄟ^兩種方法定義不同的啟動(dòng)模式:
使用AndroidManifest.xml中定義
在AndroidManifest.xml中<activity>標(biāo)簽下使用lauchMode屬性來指定當(dāng)前這個(gè)activity的啟動(dòng)模式。
使用Intent標(biāo)志定義
在調(diào)用startActivity(Intent intent)前,通過調(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中沒有,反之一樣。
我們常說的四種啟動(dòng)模式其實(shí)說的是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ù)棧:
沒有它要的任務(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:單例模式
基本上跟singleTask
相同,會(huì)為activity單獨(dú)創(chuàng)建一個(gè)任務(wù)并能夠復(fù)用。但是該模式的activity不允許其他activity跟自己存在于同一個(gè)任務(wù)中,由此 activity 啟動(dòng)的任何 activity 均會(huì)被在其他的任務(wù)中打開。
使用Intent標(biāo)志聲明啟動(dòng)模式
此方式可以通過調(diào)用intent.addFlags(int flags)
或者intent.setFlags(int flags)
方法為Intent添加一個(gè)標(biāo)志,用于為將要啟動(dòng)的Activity聲明啟動(dòng)模式。
在開始介紹前,先進(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)志中可以對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
方式中沒有與此對應(yīng)的值。
singleTask
默認(rèn)就包含了FLAG_ACTIVITY_CLEAR_TOP
的功能。
關(guān)聯(lián)任務(wù)
在分析singleTask
時(shí)有提到過該模式下啟動(dòng)activity前會(huì)去找“它想要的任務(wù)?!?,那么如何去找呢?這就引出了AndroidManifest.xml
中<activity>
標(biāo)簽下的taskAffinity
屬性。
taskAffinity 屬性
taskAffinity 屬性學(xué)名任務(wù)相關(guān)性,說白了其實(shí)就是這個(gè)參數(shù)可以指定當(dāng)前activity所屬任務(wù)棧的名字,該屬性的值為字符串:
例:android:taskAffinity="com.test.demo.task1"
如果你在<activity>
標(biāo)簽沒指定這個(gè)屬性,那么它就用<application>
標(biāo)簽的taskAffinity
屬性,如果<application>
標(biāo)簽下也沒指定,它就應(yīng)用包名當(dāng)做默認(rèn)值。
taskAffinity 與 singleTask
了解到taskAffinity
屬性后我們在重新梳理一下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)上好多人都說沒有效果,我不信就親自測試了一下得出以下結(jié)論:
剛介紹
SingleInstance
的時(shí)候說它跟singleTask
一樣都會(huì)新建一個(gè)任務(wù),既然singleTask
是根據(jù)taskAffinity
屬性來決定是否需要新建任務(wù)的,那么singleInstance
是不是也需要指定這個(gè)屬性呢?
答案是否!只要啟動(dòng)模式為singleInstance
它一定會(huì)單獨(dú)開一個(gè)任務(wù)。SingleTop
模式下指定了taskAffinity
屬性的值后,他就會(huì)神奇的切換到指定的那個(gè)任務(wù)棧中,除此之外跟原來一樣。最神奇的就是
Standard
,它也同樣受到了taskAffinity
屬性的影響,也會(huì)切換到指定的那個(gè)任務(wù)棧中,但當(dāng)我們多次啟動(dòng)這個(gè)activity時(shí)它不會(huì)再多次的創(chuàng)建實(shí)例,而是拉起了之前啟動(dòng)過的實(shí)例,更特殊的是,其他三種啟動(dòng)模式在復(fù)用之前實(shí)例時(shí)都會(huì)調(diào)用onNewIntent()
方法,他卻不會(huì)調(diào)用該方法。
taskAffinity的其他作用
taskAffinity還有一個(gè)功能就是可以重新定向所屬任務(wù),意思就是這個(gè)activity原來是屬于任務(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
屬性的配合:
- 我們在清單文件中給
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ù)
如果用戶長時(shí)間離開任務(wù),則系統(tǒng)會(huì)清除所有 Activity 的任務(wù),根 Activity 除外。 當(dāng)用戶再次返回到任務(wù)時(shí),僅恢復(fù)根 Activity。系統(tǒng)這樣做的原因是,經(jīng)過很長一段時(shí)間后,用戶可能已經(jīng)放棄之前執(zhí)行的操作,返回到任務(wù)是要開始執(zhí)行新的操作。
您可以使用下列幾個(gè) Activity 屬性修改此行為:
alwaysRetainTaskState
如果在任務(wù)的根 Activity 中將此屬性設(shè)置為 "true",則不會(huì)發(fā)生剛才所述的默認(rèn)行為。即使在很長一段時(shí)間后,任務(wù)仍將所有 Activity 保留在其堆棧中。
clearTaskOnLaunch
如果在任務(wù)的根 Activity 中將此屬性設(shè)置為 "true",則每當(dāng)用戶離開任務(wù)然后返回時(shí),系統(tǒng)都會(huì)將堆棧清除到只剩下根 Activity。 即使只離開任務(wù)片刻時(shí)間,用戶也始終會(huì)返回到任務(wù)的初始狀態(tài)。
finishOnTaskLaunch
類似于clearTaskOnLaunch,但是更狠一些,當(dāng)用戶離開任務(wù)再回來的時(shí)候,整個(gè)任務(wù)的activity都會(huì)清除,連根activity也是,相當(dāng)于第一次啟動(dòng)這個(gè)任務(wù)。
啟動(dòng)模式的實(shí)際應(yīng)用
個(gè)人覺得常見的四種啟動(dòng)模式中要屬singleTop
不是很好理解,其他的還好。
singleTop
singleTop
模式的activity特點(diǎn)就是除了外部可以啟動(dòng)它顯示信息外,它也可以用同樣的方式啟動(dòng)自己更新顯示信息,這樣就減少了冗余代碼,降低了維護(hù)成本。
例:如果讓你設(shè)計(jì)一個(gè)帶有搜索應(yīng)用的APP,主頁有一個(gè)搜索框,輸入信息點(diǎn)擊搜索按鈕進(jìn)入結(jié)果頁顯示結(jié)果,為方便用戶使用,結(jié)果頁也有一個(gè)搜索框,跟主頁的搜索框功能一樣,你會(huì)怎么設(shè)計(jì)?
singleTask
這個(gè)啟動(dòng)模式可以分為兩種情況:
- 未指定taskAffinity
此時(shí)該activity可以當(dāng)應(yīng)用中的某一模塊的入口處
有如下啟動(dòng)流程,微信主頁 >> 聊天頁 >> 聊天設(shè)置頁 >> 用戶資料頁 >> 聊天頁,此時(shí)我們按下返回鍵直接回到了微信主頁。
- 指定了taskAffinity
如果利用該啟動(dòng)模式新開了任務(wù),在用戶的視角里就相當(dāng)開了兩個(gè)應(yīng)用(在任務(wù)管理器中會(huì)看到兩個(gè)最近應(yīng)用),所以謹(jǐn)慎使用,我能想到的使用情況就是一個(gè)Word應(yīng)用打開了兩份文檔。
singleInstance
這種情況下不管有多少個(gè)任務(wù)啟動(dòng)它,它都會(huì)作為一個(gè)單獨(dú)任務(wù)存在著,這種模式極其特殊,謹(jǐn)慎使用。
例:撥號(hào)界面,鬧鐘界面。