今天想來說說Android的啟動(dòng)模式,一來自己做一個(gè)總結(jié),二來如果能幫助到別人就更好了~
首先來看一個(gè)實(shí)際的業(yè)務(wù)場景。我之前在公司實(shí)習(xí)的時(shí)候,我所在的部門只負(fù)責(zé)一個(gè)品類(國際機(jī)票),那用戶從機(jī)票首頁開始搜索機(jī)票到最終完成訂單并支付大致分為以下幾個(gè)流程(實(shí)際流程因?yàn)榭紤]的問題比較多,所以要比這稍微復(fù)雜些):
1. 用戶在機(jī)票首頁確認(rèn)好出發(fā)、到達(dá)目的地、日期及人數(shù)后,就可以點(diǎn)擊“搜索”進(jìn)入到搜索結(jié)果頁
2. 用戶根據(jù)意愿選擇相應(yīng)航班并點(diǎn)擊進(jìn)入確認(rèn)訂單頁
3. 用戶確認(rèn)好訂單內(nèi)容點(diǎn)擊下單,開始支付,支付完成跳支付完成頁
4. 用戶可以選擇回到首頁,或者去公共訂單系統(tǒng)查看訂單詳情(這屬于公共部分,不在我們討論的范圍內(nèi)了)
經(jīng)過上面4個(gè)步驟整個(gè)購票流程就算結(jié)束了。但是,這里有個(gè)問題,就是用戶從首頁到最后的支付完成頁之間經(jīng)歷了這么多的Activity,那用戶完成了支付后想回到首頁是不是要一層一層的往回退才行?如果是這樣的話,那就太不人性化了,那怎樣才能在完成支付后一鍵回到首頁呢?有人可能會(huì)說,那就直接從支付完成頁跳轉(zhuǎn)到首頁不就可以了嗎?這樣當(dāng)然是可以的,但是這樣做那之前打開的那么多Activity怎么辦呢?難道就讓它們待在Activity任務(wù)棧中嗎?這樣豈不是太浪費(fèi)內(nèi)存了!請(qǐng)看我畫的示意圖:

如上圖所示,如果我們?cè)谥Ц锻瓿芍髥?dòng)首頁Activity那頁面1-頁面5之間的所有Activity對(duì)用戶來說都是無用的,并且占用的內(nèi)存,極大地浪費(fèi)這本來可能就很緊張的Android內(nèi)存資源。那有沒有一些優(yōu)雅的方式來解決這個(gè)問題呢? 我們可以想象一下,有沒有可能在從頁面4跳轉(zhuǎn)首頁的時(shí)候把原來處于首頁之上的所有Activity全部干掉呢?這樣不就剛好解決了我們剛剛所說浪費(fèi)資源的問題了嗎?就像下圖一樣:

很顯然,無論從用戶體驗(yàn)的角度還是從內(nèi)存優(yōu)化的角度來看第二種方式都是最為優(yōu)雅的。
那怎么實(shí)現(xiàn)這種需求呢?有人可能會(huì)說,可以將任務(wù)棧中頁面1之上的所有Activity一層一層地執(zhí)行finish()方法銷毀掉,這樣就可以回退到頁面1。這樣確實(shí)是可行的,但是有個(gè)問題:如果希望從頁面4跳轉(zhuǎn)到頁面1時(shí)傳遞一些數(shù)據(jù)回去,比如說訂單號(hào)、支付完成等信息,還需要在finish()執(zhí)行前執(zhí)行setResult(int resultCode, Intent intent)將要往回傳遞的數(shù)據(jù)放在Intent里面。
這樣做其實(shí)是有風(fēng)險(xiǎn)的,因?yàn)锳ndroid系統(tǒng)并不保證Activity任務(wù)棧中那些不可見的Activity的狀態(tài)是一直被保存著的,如果出現(xiàn)系統(tǒng)內(nèi)存不足的情況,Android系統(tǒng)是可以回收那些處于不可見狀態(tài)的Activity的。也就是說,一旦中間有一Activity被銷毀了,那這個(gè)傳遞鏈就失去功效了。
那應(yīng)該怎么辦呢?別著急,Google的Android工程師早就已經(jīng)為我們想到這種需求了。那應(yīng)該怎么做呢?其實(shí)就是通過設(shè)置Activity的啟動(dòng)模式來實(shí)現(xiàn)。好,問題拋出了,下面讓我們一步一步來看。
Activity有哪幾種啟動(dòng)模式?
Activity一共有4種啟動(dòng)模式,分別是:
- standard
- singleTop
- singleTask
- singleInstance
下面我來分別做介紹。
一、standard
顧名思義,standard英文意思就是“標(biāo)準(zhǔn)的”。
也就是說這種啟動(dòng)模式是默認(rèn)的,我們平時(shí)在開發(fā)中使用最多的就是Standard模式的。
如果一個(gè)Activity的啟動(dòng)模式被設(shè)置成standard,那么它可以無限制的創(chuàng)建。你每一次通過Intent去啟動(dòng)這種模式的Activity都會(huì)重新創(chuàng)建一個(gè)。
大家可以想象一下郵箱里的收件箱(假設(shè)我們將打開郵件的Activity的啟動(dòng)模式設(shè)置為Standard,當(dāng)然這也是默認(rèn)的模式)里有10封郵件。我們給查看郵件的Activity起名為CheckEmailActivity,我點(diǎn)擊第一封郵件將會(huì)打開一個(gè)CheckEmailActivity,當(dāng)我看完之后點(diǎn)擊下一封郵件,另一個(gè)CheckEmailActivity又會(huì)被創(chuàng)建,這樣如果我們將10封郵件全部看完,那在Activity任務(wù)棧中將會(huì)有10個(gè)CheckEmailActivity,而且如果我想回到收件箱頁面還必須點(diǎn)10次返回鍵!想想是不是很可怕?
所以說standard模式雖然很常用,但也不是適用于任何場合。
另外說一點(diǎn),standard模式在Android 5.0(Lollipop)之前和之后是有區(qū)別的。
** Android Lollipop之前**
standard模式的Activity總是會(huì)被創(chuàng)建在啟動(dòng)它的Activity同一個(gè)任務(wù)棧中頂端(任務(wù)棧是一個(gè)棧結(jié)構(gòu),先進(jìn)后出 First In Last Out),就算他們來自不同的應(yīng)用。
想象一個(gè)場景,如果你在A應(yīng)用中要分享一個(gè)本地圖片,這樣會(huì)打開系統(tǒng)的圖片查看應(yīng)用中的圖片選擇器Activity,雖然這兩個(gè)Activity來自不同的應(yīng)用,但Android系統(tǒng)仍將會(huì)把他們放在同一個(gè)任務(wù)棧中,即A應(yīng)用的任務(wù)棧中。

Android Lollipop之后
如果將要啟動(dòng)的Activity和啟動(dòng)它的Activity來自同一個(gè)應(yīng)用,那沒話說,和Lollipop之前一樣,新的Activity會(huì)被創(chuàng)建在當(dāng)前任務(wù)棧中的頂端。
但是如果它們來自不同的應(yīng)用,那就會(huì)創(chuàng)建一個(gè)新的任務(wù)棧,再把要啟動(dòng)的Activity放在新的任務(wù)棧中,這時(shí)這個(gè)新啟動(dòng)的Activity就是新創(chuàng)建的任務(wù)站點(diǎn)的根Activity。如下圖所示:

二、singleTop
顧名思義,singleTop的意思就是“在頂部只能有一個(gè)”。
這種啟動(dòng)模式非常類似于standard,但是也有一些 區(qū)別:
如果在啟動(dòng)這種模式的Activity的時(shí)候,當(dāng)前任務(wù)棧的頂端已經(jīng)存在了相同的Activity,那系統(tǒng)就不會(huì)再創(chuàng)建新的,而是回調(diào)任務(wù)棧中已經(jīng)存在的該Activity的onNewIntent( )方法。請(qǐng)看下面的示意圖:

也正因?yàn)镾ingleTop啟動(dòng)模式的特殊性,所以在開發(fā)時(shí),如果指定了一個(gè)Activity的啟動(dòng)模式是singleTop的那就應(yīng)該既要重寫onCreated()方法用于應(yīng)對(duì)第一次創(chuàng)建的情況,也要重寫onNewIntent( )方法來應(yīng)對(duì)重復(fù)創(chuàng)建的情況。
其實(shí)大家可以想象一下,這種啟動(dòng)模式的應(yīng)用場景。Android既然提供了這種啟動(dòng)模式,說明肯定有應(yīng)有場景需要這樣的方式。其實(shí)最常用的場景就是搜索,比方說我們?cè)谒阉骺蛑休斎胂胍阉鞯膬?nèi)容點(diǎn)擊搜索進(jìn)入SearchResultActivty(搜索結(jié)果頁)查看搜索的結(jié)果(一般我們也會(huì)在搜索結(jié)果頁提供搜索框,這樣用戶無需點(diǎn)擊返回鍵回到上一個(gè)頁面再在搜索框中輸入搜索內(nèi)容點(diǎn)擊搜索),如果此時(shí)用戶還想搜點(diǎn)別的東西,就可以直接在當(dāng)前的搜索結(jié)果頁SearchResultActivty中的搜索框輸入搜索內(nèi)容繼續(xù)搜索。
大家想象一下,如果我們把SearchResultActivty的啟動(dòng)模式設(shè)置為Standard的話會(huì)是什么樣的景象。比如我們連著搜了10個(gè)內(nèi)容,那就會(huì)啟動(dòng)10個(gè)不同的SearchResultActivty,然而這些SearchResultActivty功能完全一樣,完全沒有必要?jiǎng)?chuàng)建這么多,而且還有一個(gè)和上一節(jié)中的郵箱一樣的問題,就是用戶搜索結(jié)束想回到首頁,那就還得按10次返回鍵才能回到首頁,- -!
這時(shí),singleTop啟動(dòng)模式就派上用場了,我們首先把SearchResultActivty的啟動(dòng)模式設(shè)置為singleTop,這樣用戶在SearchResultActivty頁面中繼續(xù)搜索的時(shí)候,我們只需把用戶要搜索的內(nèi)容放在Intent里面然后啟動(dòng)SearchResultActivty,這時(shí)系統(tǒng)并不會(huì)重新創(chuàng)建新的SearchResultActivty,而是回調(diào)當(dāng)前任務(wù)棧棧頂?shù)腟earchResultActivty的onNewIntent()方法來接收帶有用戶搜索內(nèi)容信息的Intent,然后我們拿到用戶搜索內(nèi)容后調(diào)搜索接口,并根據(jù)接口返回內(nèi)容重新刷新布局即可,似不似很神奇?其實(shí)我們?cè)谏弦还?jié)提到的郵箱的問題,也是用這種方式來解決的,原理和搜索一樣的。
三、singleTask
這種啟動(dòng)模式的Activity在Android系統(tǒng)中只允許存在一個(gè)實(shí)例。
如果系統(tǒng)中已經(jīng)存在了該種啟動(dòng)模式的目標(biāo)Activity,則系統(tǒng)并不會(huì)重新創(chuàng)建一個(gè)目標(biāo)Activity,而是首先將持有目標(biāo)Activity的整個(gè)任務(wù)棧都會(huì)被置于前臺(tái)(用戶可見),并且通過onNewIntent( )方法將啟動(dòng)目標(biāo)Activity的Intent傳遞給目標(biāo)Activity,置于目標(biāo)Activity拿到這個(gè)Intent之后要做什么操作,系統(tǒng)就不管了,隨便你拿來干什么,哼~。
但是這里有個(gè)問題,就是目標(biāo)Activity和源Activity是不是來自同一應(yīng)用。
源Activity和目標(biāo)Activity來自同一個(gè)應(yīng)用
這種情況還要分兩種情況說:
當(dāng)前系統(tǒng)中還沒有目標(biāo)Activity的實(shí)例
這種情況最簡單,直接在當(dāng)前的任務(wù)棧中創(chuàng)建SingleTask模式的Activity并置于棧頂即可。
當(dāng)前系統(tǒng)中已經(jīng)存在目標(biāo)Activity的實(shí)例
這種情況比較特殊,因?yàn)橄到y(tǒng)會(huì)把任務(wù)棧中目標(biāo)Activity之上的所有Activity銷毀,以讓目標(biāo)Activity處在棧頂?shù)奈恢谩?/p>
這里還要還要再提醒大家的是,因?yàn)槟繕?biāo)Activity已經(jīng)存在,系統(tǒng)不會(huì)重新創(chuàng)建,而是通過onNewIntent()的方式把Intent傳遞過來,這點(diǎn)和singleTop模式有些類似。注意了,這里讓我們回想一下文章開頭的我所說的場景,如何讓用戶在支付完成頁直接跳轉(zhuǎn)到首頁,并把不需要的Activity銷毀?SingleTask啟動(dòng)模式是不是剛好和我們的需求一致?請(qǐng)看下面的示意圖:

源Activity和目標(biāo)Activity來自不同應(yīng)用
這種情況也要分兩種情況說:
當(dāng)前系統(tǒng)中還沒有目標(biāo)Activity的實(shí)例
這時(shí)系統(tǒng)首先會(huì)看任務(wù)管理器中是否有目標(biāo)Actvity所在應(yīng)用的任務(wù)棧?如果有的話,那就直接在目標(biāo)Activity所在應(yīng)用的任務(wù)棧的棧頂創(chuàng)建即可。
如果任務(wù)管理器中沒有目標(biāo)Activity所在應(yīng)用的任務(wù)棧,系統(tǒng)就會(huì)創(chuàng)建其所在應(yīng)用的任務(wù)棧和目標(biāo)Activity,并且把目標(biāo)Activity作為新建任務(wù)棧的根Activity。如下圖所示:

** 當(dāng)前系統(tǒng)中已經(jīng)存在目標(biāo)Activity的實(shí)例**
目標(biāo)Activity所在任務(wù)棧會(huì)被置于前臺(tái)(即用戶可見),而且也會(huì)把目標(biāo)Activity之上的所有Actvity全部銷毀。
四、singleInstance
這種啟動(dòng)模式和singleTask幾乎一樣,它也只允許系統(tǒng)中存在一個(gè)目標(biāo)Activity,包括上面我們所說的SingleTask的一些特性singleInstance都有。唯一不同的是,持有目標(biāo)Activity的任務(wù)棧中只能有目標(biāo)Activity一個(gè)Actvitiy,不能再有別的Activity,對(duì)! 就是承包了這個(gè)任務(wù)棧!哈哈~。
其實(shí)從這種啟動(dòng)模式的名字也可以看出來它表示的意思,singleInstance直譯過來就是“單一實(shí)例”,什么意思呢?這話啊有兩層意思,我來給你分析分析:1. 跟系統(tǒng)說,“我是獨(dú)一無二的,不許和我一樣的人存在!”,這就是說系統(tǒng)中存在一個(gè)目標(biāo)Activity。;2. 跟任務(wù)棧說,“我是獨(dú)一無二的,不許你心里再裝別的人!”,這就是說持有目標(biāo)Activity的任務(wù)棧中只能有目標(biāo)Activity一個(gè)Activity。這樣說是不是好理解一些,哈哈~
所以,如果要啟動(dòng)singleInstance模式的Activity,那只能新創(chuàng)建一個(gè)任務(wù)棧用來放它,因?yàn)槿思艺f了,“我是獨(dú)一無二的!”。同樣的,如果從這種啟動(dòng)模式的Activity中啟動(dòng)別的Activity,那不好意思,我不管你是不是和我處在同一個(gè)應(yīng)用,我所在的任務(wù)棧只能擁有我一個(gè)人,您吶,另外讓系統(tǒng)給你創(chuàng)建一個(gè)任務(wù)棧待著去吧。
好了,至此我們介紹了Activity的4種啟動(dòng)模式了,也大致了解了每種啟動(dòng)模式的特點(diǎn)了,那接下里的問題就是怎么使用呢?問題又拋出來了,好,讓我們接著往下看。
怎么使用啟動(dòng)模式?
有兩種方式來使用或者說設(shè)置Activity的啟動(dòng)模式:
方式1:在AndroidMenifest.xml文件中設(shè)置:

看到?jīng)]有,在<activity>標(biāo)簽中設(shè)置android:launchMode="****"屬性即可,****即我們上面所說的四種啟動(dòng)模式。
方式2:通過為Intent添加標(biāo)識(shí)來設(shè)置

看到?jīng)]有,這里使用Intent的addFlags()方法來添加一些標(biāo)志,其實(shí)這個(gè)addFlags()不光可以用來設(shè)置Activity的啟動(dòng)模式,還能做很多事情,它的作用是給Intent添加一些附加屬性。具體的可以參見Android api哈~
那我們想設(shè)置Activity的啟動(dòng)模式應(yīng)該給addFlags()方法設(shè)置哪些參數(shù)呢?來,接著往下看:
FLAG_ACTIVITY_NEW_TASK
與"singleTask"啟動(dòng)模式的作用一樣。FLAG_ACTIVITY_SINGLE_TOP
與"singleTop"啟動(dòng)模式的作用一樣。FLAG_ACTIVITY_CLEAR_TOP
這個(gè)標(biāo)識(shí)的意思比較特殊。它不對(duì)應(yīng)于我們上面所說的啟動(dòng)模式中的任何一種,我們來看一下android api中對(duì)這個(gè)標(biāo)識(shí)的說明:
“如果正在啟動(dòng)的 Activity 已在當(dāng)前任務(wù)中運(yùn)行,則會(huì)銷毀當(dāng)前任務(wù)頂部的所有 Activity,并通過onNewIntent()
將此 Intent 傳遞給 Activity 已恢復(fù)的實(shí)例(現(xiàn)在位于頂部),而不是啟動(dòng)該 Activity 的新實(shí)例。”
大家可能會(huì)發(fā)現(xiàn),通過addFlags()的方式來設(shè)置啟動(dòng)模式有局限性,只能顯示的設(shè)置“singleTask”和“singleTop”兩種啟動(dòng)模式,而并沒有對(duì)應(yīng)“standard”和“singleInstance”啟動(dòng)模式的標(biāo)識(shí)。是的,android api文檔中確實(shí)只只有以上三種標(biāo)識(shí)用來設(shè)置啟動(dòng)模式,而且第三種“**FLAG_ACTIVITY_CLEAR_TOP
**”還不對(duì)應(yīng)任何一種啟動(dòng)模式,難道可以算作第5種啟動(dòng)模式?遺憾的是,我現(xiàn)在也不清楚是怎么回事。
但是實(shí)際開發(fā)中,我們一般都是在AndroidMenifest.xml文件中去設(shè)置Activity的啟動(dòng)模式。
好了,文章差不多就寫到這吧。謝謝觀看~
多說一句,關(guān)于Android任務(wù)和返回棧的相關(guān)知識(shí)可以參見Android api文檔,上面說的很詳細(xì),其實(shí)我習(xí)慣把返回棧說成是任務(wù)棧,不知道這樣是不是合適,嘿嘿~
參考文獻(xiàn):
https://developer.android.com/guide/topics/manifest/activity-element.html
https://developer.android.com/guide/components/tasks-and-back-stack.html
https://inthecheesefactory.com/blog/understand-android-activity-launchmode/en