這是一個(gè)針對(duì)技術(shù)開(kāi)發(fā)者的一個(gè)應(yīng)用畔濒,你可以在掘金上獲取最新最優(yōu)質(zhì)的技術(shù)干貨,不僅僅是Android知識(shí)锣咒、前端侵状、后端以至于產(chǎn)品和設(shè)計(jì)都有涉獵,想成為全棧工程師的朋友不要錯(cuò)過(guò)毅整!
英文原文:Understand Android Activity's launchMode: standard, singleTop, singleTask and singleInstance另外關(guān)于啟動(dòng)模式還有篇很好的文章:Android中Activity四種啟動(dòng)模式和taskAffinity屬性詳解
Activity是安卓上最聰明的設(shè)計(jì)之一趣兄,優(yōu)秀的內(nèi)存管理讓多任務(wù)完美運(yùn)行在最流行的操作系統(tǒng)之上。并不是讓Activity在屏幕上啟動(dòng)就完事了毛嫉,其啟動(dòng)方式也是需要關(guān)注的诽俯。這個(gè)話題的內(nèi)容很多,其中很重要的就是啟動(dòng)模式(launchMode)承粤。這也是我們這篇博客要討論的內(nèi)容。
因?yàn)椴煌腁ctivity有不同的目的闯团。有些被設(shè)計(jì)成每發(fā)送一個(gè)intent都單獨(dú)一個(gè)Activity工作辛臊,比如郵件客戶端中撰寫(xiě)郵件的Activity,而有些則被設(shè)計(jì)成單例的房交,比如郵件收件箱的Activity彻舰。
這就是為什么指明一個(gè)Activity是否需要新建還是使用現(xiàn)有Activity是很有必要的,否則可能導(dǎo)致糟糕的用戶體驗(yàn)。多虧了安卓的核心工程師刃唤,讓launchMode可以幫助你專門(mén)應(yīng)對(duì)這種情況隔心。
設(shè)置一個(gè)launchMode
一般地,我們可以直接在AndroidManifest.xml 標(biāo)簽的一個(gè)屬性中設(shè)置launchMode尚胞,如下:
android:name=".SingleTaskActivity"
android:label="singleTask?launchMode"
android:launchMode="singleTask">
有4種類型的launchMode硬霍,我們一個(gè)一個(gè)的看。
standard
這是默認(rèn)的模式笼裳。
這種模式下唯卖,當(dāng)Intent發(fā)送的時(shí)候,Activity總是被創(chuàng)建一個(gè)新的出來(lái)單獨(dú)工作躬柬。想象一下拜轨,如果有發(fā)送10個(gè)撰寫(xiě)郵件的Intent,那么將有10個(gè)不同的Activity啟動(dòng)允青。
在Lollipop之前設(shè)備上的表現(xiàn)
這種Activity將被創(chuàng)建并置于棧頂橄碾,和發(fā)送intent的Activity處于同一個(gè)任務(wù)中。注:一般來(lái)講颠锉,安卓第三個(gè)虛擬鍵所列出的那些就是任務(wù)法牲。
下面的圖片顯示了向標(biāo)準(zhǔn)啟動(dòng)模式的Activity分享照片時(shí)的情況。雖然分別來(lái)自不同的應(yīng)用木柬,但仍然它會(huì)和發(fā)送intent的Activity處于同一個(gè)任務(wù)中皆串。
注:從圖中可以看出分享圖片的是Gallery應(yīng)用。
同時(shí)你會(huì)看到此時(shí)任務(wù)管理器是這樣的(有一點(diǎn)怪異)眉枕。
如果我們切換到另外一個(gè)應(yīng)用然后再切回到Gallery恶复,你會(huì)發(fā)現(xiàn)standard launchMode啟動(dòng)的Activity仍然在Gallery任務(wù)的上面,導(dǎo)致在操作Gallery之前速挑,我們必須首先結(jié)束這個(gè)額外的Activity谤牡。
在Lollipop設(shè)備上的表現(xiàn)
如果Activity都是來(lái)自同一個(gè)應(yīng)用,其表現(xiàn)和Lollipop之前的設(shè)備一樣姥宝,在任務(wù)的頂端翅萤。
但是如果intent來(lái)自其他應(yīng)用,將創(chuàng)建一個(gè)新的任務(wù)腊满,同時(shí)新創(chuàng)建的Activity會(huì)被作為一個(gè)根Activity套么,如下:
注:圖片中的Task#2和Task#3分別表示兩個(gè)任務(wù),序號(hào)大的比序號(hào)小的后啟動(dòng)碳蛋。
下面是任務(wù)管理器中的樣子:
發(fā)生這種情況的原因是Lollipop中任務(wù)管理系統(tǒng)做了修改胚泌,讓它看起來(lái)更合理了。因?yàn)樗鼈冊(cè)诓煌娜蝿?wù)中肃弟,你可以直接切回Gallery玷室,你還可以觸發(fā)另一個(gè)Intent零蓉,創(chuàng)建新的與之前相同的任務(wù)。
撰寫(xiě)郵件的Activity或者發(fā)布社交網(wǎng)絡(luò)狀態(tài)的Activity都是采用這種Activity的例子穷缤。如果你希望Activity單獨(dú)服務(wù)于一個(gè)Intent敌蜂,就可以考慮standard啟動(dòng)模式。
singleTop
接下來(lái)就是singleTop模式津肛。它的表現(xiàn)幾乎和standard模式一模一樣章喉,一個(gè)singleTop Activity 的實(shí)例可以無(wú)限多,唯一的區(qū)別是如果在棧頂已經(jīng)有一個(gè)相同類型的Activity實(shí)例快耿,Intent不會(huì)再創(chuàng)建一個(gè)Activity囊陡,而是通過(guò)onNewIntent()被發(fā)送到現(xiàn)有的Activity。
在singleTop模式下我們需要同時(shí)在onCreate() 和 onNewIntent()中處理發(fā)來(lái)的intent掀亥,以滿足不同情況撞反。
這種啟動(dòng)模式的用例之一就是搜索功能。假設(shè)我們創(chuàng)建了一個(gè)搜索框搪花,點(diǎn)擊搜索的時(shí)候?qū)?dǎo)航到一個(gè)顯示搜索結(jié)果列表的SearchActivity中遏片,為了更好的用戶體驗(yàn),這個(gè)搜索框一般也會(huì)被放到SearchActivity中撮竿,這樣用戶想要再次搜索就不需要按返回鍵吮便。
想像一下,如果每次顯示搜索結(jié)果的時(shí)候我們都啟動(dòng)一個(gè)新的activity幢踏,10次搜索10個(gè)activity髓需,那樣當(dāng)我們想返回最初的那個(gè)activity的時(shí)候需要按10次返回。
所以我們應(yīng)該這樣房蝉,如果棧頂已經(jīng)有一個(gè)SearchActivity僚匆,我們將Intent發(fā)送給現(xiàn)有的activity,讓它來(lái)更新搜索結(jié)果搭幻。這樣就只會(huì)有一個(gè)在棧頂?shù)腟earchActivity咧擂,只需點(diǎn)一次back就可以回到之前的activity。
不管怎樣檀蹋,singleTop和它的調(diào)用者處在一個(gè)任務(wù)中松申。如果你想要讓intent發(fā)送給另一個(gè)任務(wù)中處于棧頂?shù)腁ctivity,是不行的俯逾。
而當(dāng)Intent來(lái)自于另外一個(gè)應(yīng)用的時(shí)候贸桶,新的Activity的啟動(dòng)方式和standard模式是一致的(pre-Lollipop:處于調(diào)用者任務(wù)的棧頂,Lollipop:會(huì)創(chuàng)建一個(gè)新的任務(wù))桌肴。
singleTask
這種模式和standard以及singleTop有很大不同刨啸。singleTask模式的Activity只允許在系統(tǒng)中有一個(gè)實(shí)例。如果系統(tǒng)中已經(jīng)有了一個(gè)實(shí)例识脆,持有這個(gè)實(shí)例的任務(wù)將移動(dòng)到頂部,同時(shí)intent將被通過(guò)onNewIntent()發(fā)送。如果沒(méi)有灼捂,則會(huì)創(chuàng)建一個(gè)新的Activity并置放在合適的任務(wù)中离例。
在同一個(gè)應(yīng)用中的情況
如果系統(tǒng)中還沒(méi)有singleTask的Activity,會(huì)新創(chuàng)建一個(gè)悉稠,并放在同一任務(wù)的棧頂宫蛆。
但是如果已經(jīng)存在,singleTask Activity上面的所有Activity將以合適的方式自動(dòng)銷毀的猛,讓我們想要顯示的Activity處于棧頂耀盗。同時(shí)Intent也會(huì)通過(guò)onNewIntent()方法發(fā)送到這個(gè)singleTask Activity。
在用戶體驗(yàn)方面卦尊,可能不是很合理叛拷,但是它就是這樣設(shè)計(jì)的...
你可能注意到了官方文檔中提到的一個(gè)問(wèn)題:
系統(tǒng)會(huì)創(chuàng)建一個(gè)新的任務(wù),并將這個(gè)Activity實(shí)例化為新任務(wù)的根部(root)岂却。
但從實(shí)驗(yàn)結(jié)果來(lái)看忿薇,并不是這么回事。singleTask Activity仍然在任務(wù)的Activity棧頂躏哩,我們可以從dumpsys activity 命令顯示上看出來(lái):
Taskid#239
TaskRecord{428efe30#239?A=com.thecheesefactory.lab.launchmode?U=0?sz=2}
Intent
{act=android.intent.action.MAIN?cat=[android.intent.category.LAUNCHER]
flg=0x10000000
cmp=com.thecheesefactory.lab.launchmode/.StandardActivity}
Hist#1:?ActivityRecord{429a88d0?u0?com.thecheesefactory.lab.launchmode/.SingleTaskActivity?t239}
Intent{cmp=com.thecheesefactory.lab.launchmode/.SingleTaskActivity}
ProcessRecord{4224313018965:com.thecheesefactory.lab.launchmode/u0a123}
Hist#0:?ActivityRecord{425fec98?u0?com.thecheesefactory.lab.launchmode/.StandardActivity?t239}
Intent
{act=android.intent.action.MAIN?cat=[android.intent.category.LAUNCHER]
flg=0x10000000
cmp=com.thecheesefactory.lab.launchmode/.StandardActivity}
ProcessRecord{4224313018965:com.thecheesefactory.lab.launchmode/u0a123}
如果你希望singleTask Activity表現(xiàn)的和文檔中描述的一致署浩,你需要為singleTask Activity設(shè)置taskAffinity屬性。
android:name=".SingleTaskActivity"
android:label="singleTask?launchMode"
android:launchMode="singleTask"
android:taskAffinity="">
這里是啟動(dòng)SingleTaskActivity的的結(jié)果(使用了taskAffinity之后)扫尺。
是否使用taskAffinity取決于你自己筋栋。
和其他應(yīng)用一起工作的情況
一旦intent是從另外的應(yīng)用發(fā)送過(guò)來(lái),并且系統(tǒng)中也沒(méi)有任何Activity的實(shí)例正驻,則會(huì)創(chuàng)建一個(gè)新的任務(wù)弊攘,并且新的Activity被作為根Activity創(chuàng)建。
除非擁有這個(gè)singleTask Activity 的應(yīng)用已經(jīng)存在拨拓,那樣的話肴颊,新建的Activity會(huì)置于這個(gè)任務(wù)的上面(而不是新建一個(gè)任務(wù))。
In case that there is an Activity instance existed?in any Task, the whole Task would be moved to top and every single Activity placed above the singleTask Activity will be destroyed with?lifecycle.If back button is pressed, user has to travel through the Activities in the stack before going back to the caller?Task.
假設(shè)已經(jīng)有了一個(gè)Activity的實(shí)例渣磷,不管它是在哪個(gè)任務(wù)中(包括上面的那種情況婿着,在用于這個(gè)Activity的應(yīng)用中),整個(gè)任務(wù)將被移到頂端醋界,而singleTask ?Activity上面的所有 Activity 都將被銷毀竟宋, 用戶需要按back鍵遍歷玩棧中的Activity才能回到調(diào)用者任務(wù)。
這種模式的應(yīng)用案例有形纺。郵件客戶端的收件箱或者社交網(wǎng)絡(luò)的時(shí)間軸丘侠。這些Activity一般不會(huì)設(shè)計(jì)成擁有多個(gè)實(shí)例,singleTask可以滿足逐样。但是在使用這種模式的時(shí)候必須要明智蜗字,因?yàn)橛行〢ctivity會(huì)在用戶不知情的情況下被銷毀打肝。
singleInstance
這個(gè)模式非常接近于singleTask,系統(tǒng)中只允許一個(gè)Activity的實(shí)例存在挪捕。區(qū)別在于持有這個(gè)Activity的任務(wù)中只能有一個(gè)Activity:即這個(gè)單例本身粗梭。If?another Activity is called from this kind of Activity, a new Task would be automatically created to place that new Activity. Likewise, if singleInstance Activity is called, new Task would be created to place the Activity.
不過(guò)結(jié)果卻很怪異,從dumpsys提供的信息來(lái)看级零,似乎系統(tǒng)中有兩個(gè)任務(wù)但任務(wù)管理器中只顯示一個(gè),即最后被移到頂部的那個(gè)断医。導(dǎo)致雖然后臺(tái)有一個(gè)任務(wù)在運(yùn)行,我們卻無(wú)法切換回去奏纪,這一點(diǎn)也不科學(xué)鉴嗤。
下面是當(dāng)singleInstance Activity被調(diào)用的同時(shí)棧中已經(jīng)有一些Activity的情況下所發(fā)生的事情:
本來(lái)有兩個(gè)任務(wù),但是任務(wù)管理器中卻只顯示一個(gè)任務(wù):
Since this Task could has only one Activity, we couldn't switch back to Task #1 anymore. Only?way to do so is to relaunch the application from launcher but it appears that the singleInstance Task would be hidden in the background instead.
因?yàn)檫@個(gè)任務(wù)只有一個(gè)Activity序调,我們?cè)僖矡o(wú)法切回到任務(wù)#1了醉锅。唯一的辦法是重新在launcher中啟動(dòng)這個(gè)應(yīng)用。?but之后的沒(méi)有翻譯炕置,因?yàn)槲乙膊幻靼鬃髡叩囊馑肌?/p>
不過(guò)這個(gè)問(wèn)題也有解決方案荣挨,就像我們?cè)趕ingleTask Acvity中做的,只要為singleInstance Activity設(shè)置taskAffinity屬性就可以了朴摊。
android:name=".SingleInstanceActivity"
android:label="singleInstance?launchMode"
android:launchMode="singleInstance"
android:taskAffinity="">
現(xiàn)在科學(xué)多了默垄。
這種模式很少被使用。實(shí)際使用的案例如Launcher的Activity或者100%確定只有一個(gè)Activity的應(yīng)用甚纲】诙В總之除非完全有必要,不然我不建議使用這種模式介杆。
Intent Flags
除了在AndroidManifest.xml中直接設(shè)置launch mode鹃操,我們還可以通過(guò)叫做Intent Flags的東西設(shè)置更多的行為,比如:
Intentintent=newIntent(StandardActivity.this,StandardActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
這段代碼將會(huì)啟動(dòng)一個(gè)singleTop啟動(dòng)模式的的StandardActivity春哨。
有許多種Flag可以使用荆隘,更多的請(qǐng)參考Intent。
希望這篇文章對(duì)你有用赴背。