任務(wù)棧是什么
任務(wù)棧Task擂啥,是一種用來(lái)放置Activity實(shí)例的容器悯辙,他是以棧的形式進(jìn)行盛放遗锣,也就是所謂的先進(jìn)后出货裹,主要有2個(gè)基本操作:壓棧和出棧,其所存放的Activity是不支持重新排序的精偿,只能根據(jù)壓棧和出棧操作更改Activity的順序弧圆。
啟動(dòng)一個(gè)Application的時(shí)候,系統(tǒng)會(huì)為它默認(rèn)創(chuàng)建一個(gè)對(duì)應(yīng)的Task笔咽,用來(lái)放置根Activity搔预。默認(rèn)啟動(dòng)Activity會(huì)放在同一個(gè)Task中,新啟動(dòng)的Activity會(huì)被壓入啟動(dòng)它的那個(gè)Activity的棧中叶组,并且顯示它拯田。當(dāng)用戶按下回退鍵時(shí),這個(gè)Activity就會(huì)被彈出棧甩十,按下Home鍵回到桌面船庇,再啟動(dòng)另一個(gè)應(yīng)用,這時(shí)候之前那個(gè)Task就被移到后臺(tái)侣监,成為后臺(tái)任務(wù)棧鸭轮,而剛啟動(dòng)的那個(gè)Task就被調(diào)到前臺(tái),成為前臺(tái)任務(wù)棧橄霉,Android系統(tǒng)顯示的就是前臺(tái)任務(wù)棧中的Top實(shí)例Activity窃爷。
任務(wù)棧的作用
以往基于應(yīng)用(application)的程序開(kāi)發(fā)中,程序具有明確的邊界姓蜂,一個(gè)程序就是一個(gè)應(yīng)用按厘,一個(gè)應(yīng)用為了實(shí)現(xiàn)功能可以采用開(kāi)辟新線程甚至新進(jìn)程來(lái)輔助,但是應(yīng)用與應(yīng)用之間不能復(fù)用資源和功能覆糟。而Android引入了基于組件開(kāi)發(fā)的軟件架構(gòu)刻剥,雖然我們開(kāi)發(fā)android程序,仍然使用一個(gè)apk工程一個(gè)Application的開(kāi)發(fā)形式滩字,但是對(duì)于Aplication的開(kāi)發(fā)就用到了Activity造虏、service等四大組件,其中的每一個(gè)組件麦箍,都是可以被跨應(yīng)用復(fù)用的漓藕,這就是android的神奇之處。雖然組件可以跨應(yīng)用被調(diào)用挟裂,但是一個(gè)組件所在的進(jìn)程必須是在組件所在的Aplication進(jìn)程中享钞。由于android強(qiáng)化了組件概念,弱化了Aplication的概念诀蓉,所以在android程序開(kāi)發(fā)中栗竖,A應(yīng)用的A組件想要使用拍照或錄像的功能就可以不用去針對(duì)Camera類進(jìn)行開(kāi)發(fā)暑脆,直接調(diào)用系統(tǒng)自帶的攝像頭應(yīng)用(稱其B應(yīng)用)中的組件(稱其B組件)就可以了,但是這就引發(fā)了一個(gè)新問(wèn)題狐肢,A組件運(yùn)行在A應(yīng)用中添吗,B組件運(yùn)行在B應(yīng)用中,自然都不在同一個(gè)進(jìn)程中份名,那么從B組件中返回的時(shí)候碟联,如何實(shí)現(xiàn)正確返回到A組件呢?Task就是來(lái)負(fù)責(zé)實(shí)現(xiàn)這個(gè)功能的僵腺,它是從用戶角度來(lái)理解應(yīng)用而建立的一個(gè)抽象概念鲤孵。因?yàn)橛脩羲芸吹降慕M件就是Activity,所以Task可以理解為實(shí)現(xiàn)一個(gè)功能而負(fù)責(zé)管理所有用到的Activity實(shí)例的棧辰如。
棧是一個(gè)先進(jìn)后出的線性表普监,根據(jù)Activity在當(dāng)前棧結(jié)構(gòu)中的位置,來(lái)決定該Activity的狀態(tài)丧没。正常情況下鹰椒,當(dāng)一個(gè)Activity啟動(dòng)了另一個(gè)Activity的時(shí)候锡移,新啟動(dòng)的Activity就會(huì)置于任務(wù)棧的頂端呕童,并處于活動(dòng)狀態(tài),而啟動(dòng)它的Activity雖然成功身退淆珊,但依然保留在任務(wù)棧中夺饲,處于停止?fàn)顟B(tài),當(dāng)用戶按下返回鍵或者調(diào)用finish()方法時(shí)施符,系統(tǒng)會(huì)移除頂部Activity往声,讓后面的Activity恢復(fù)活動(dòng)狀態(tài)牧牢。當(dāng)然作箍,世界不可能一直這么“和諧”,可以給Activity設(shè)置一些“特權(quán)”谴麦,來(lái)打破這種“和諧”的模式听哭,這種特權(quán)慢洋,就是通過(guò)在AndroidManifest文件中的屬性andorid:launchMode來(lái)設(shè)置或者通過(guò)Intent的flag來(lái)設(shè)置的,下面就先介紹下Activity的幾種啟動(dòng)模式陆盘。
standard
默認(rèn)模式普筹,可以不用寫(xiě)配置。在這個(gè)模式下隘马,都會(huì)默認(rèn)創(chuàng)建一個(gè)新的實(shí)例太防。因此,在這種模式下酸员,可以有多個(gè)相同的實(shí)例蜒车,也允許多個(gè)相同Activity疊加讳嘱。應(yīng)用場(chǎng)景:絕大多數(shù)Activity。
如果以這種方式啟動(dòng)的Activity被跨進(jìn)程調(diào)用酿愧,在5.0之前新啟動(dòng)的Activity實(shí)例會(huì)放入發(fā)送Intent的Task的棧的頂部呢燥,盡管它們屬于不同的程序,這似乎有點(diǎn)費(fèi)解看起來(lái)也不是那么合理寓娩,所以在5.0之后叛氨,上述情景會(huì)創(chuàng)建一個(gè)新的Task,新啟動(dòng)的Activity就會(huì)放入剛創(chuàng)建的Task中棘伴,這樣就合理的多了寞埠。
singleTop
棧頂復(fù)用模式,如果要開(kāi)啟的activity在任務(wù)棧的頂部已經(jīng)存在焊夸,就不會(huì)創(chuàng)建新的實(shí)例仁连,而是調(diào)用 onNewIntent() 方法。避免棧頂?shù)腶ctivity被重復(fù)的創(chuàng)建阱穗。應(yīng)用場(chǎng)景:在通知欄點(diǎn)擊收到的通知饭冬,然后需要啟動(dòng)一個(gè)Activity,這個(gè)Activity就可以用singleTop揪阶,否則每次點(diǎn)擊都會(huì)新建一個(gè)Activity昌抠。當(dāng)然實(shí)際的開(kāi)發(fā)過(guò)程中,測(cè)試妹紙沒(méi)準(zhǔn)給你提過(guò)這樣的bug:某個(gè)場(chǎng)景下連續(xù)快速點(diǎn)擊鲁僚,啟動(dòng)了兩個(gè)Activity炊苫。如果這個(gè)時(shí)候待啟動(dòng)的Activity使用 singleTop模式也是可以避免這個(gè)Bug的。
同standard模式冰沙,如果是外部程序啟動(dòng)singleTop的Activity侨艾,在Android 5.0之前新創(chuàng)建的Activity會(huì)位于調(diào)用者的Task中,5.0及以后會(huì)放入新的Task中拓挥。
singleTask
棧內(nèi)復(fù)用模式唠梨, activity只會(huì)在任務(wù)棧里面存在一個(gè)實(shí)例。如果要激活的activity侥啤,在任務(wù)棧里面已經(jīng)存在当叭,就不會(huì)創(chuàng)建新的activity,而是復(fù)用這個(gè)已經(jīng)存在的activity愿棋,調(diào)用 onNewIntent() 方法科展,并且清空這個(gè)activity任務(wù)棧上面所有的activity。應(yīng)用場(chǎng)景:大多數(shù)App的主頁(yè)糠雨。對(duì)于大部分應(yīng)用才睹,當(dāng)我們?cè)谥鹘缑纥c(diǎn)擊回退按鈕的時(shí)候都是退出應(yīng)用,那么當(dāng)我們第一次進(jìn)入主界面之后,主界面位于棧底琅攘,以后不管我們打開(kāi)了多少個(gè)Activity垮庐,只要我們?cè)俅位氐街鹘缑妫紤?yīng)該使用將主界面Activity上所有的Activity移除的方式來(lái)讓主界面Activity處于棧頂坞琴,而不是往棧頂新加一個(gè)主界面Activity的實(shí)例哨查,通過(guò)這種方式能夠保證退出應(yīng)用時(shí)所有的Activity都能報(bào)銷毀。在跨應(yīng)用Intent傳遞時(shí)剧辐,如果系統(tǒng)中不存在singleTask Activity的實(shí)例寒亥,那么將創(chuàng)建一個(gè)新的Task,然后創(chuàng)建SingleTask Activity的實(shí)例荧关,將其放入新的Task中溉奕。
1:假如目前有個(gè)任務(wù)棧T1中的情況是ABC,這個(gè)時(shí)候ActivityD以singleTask模式請(qǐng)求啟動(dòng)忍啤,其所需要的任務(wù)棧正是T1加勤,則系統(tǒng)會(huì)直接創(chuàng)建D的實(shí)例并將其入棧到T1中。
2:假如DActivity啟動(dòng)所需要的任務(wù)棧為T(mén)2,由于T2和D的實(shí)例均不存在同波,那么系統(tǒng)會(huì)先創(chuàng)建任務(wù)棧T2鳄梅,然后再創(chuàng)建D的實(shí)例并將其入棧到T2中。我們可以通過(guò)設(shè)置Activity的taskAffinity屬性來(lái)模擬這一場(chǎng)景未檩。
<activity android:name=".SingleTaskActivity" android:label="singleTask launchMode" android:launchMode="singleTask" android:taskAffinity=""></activity>
singleTask2.png
3:如果D所需的任務(wù)棧為T(mén)3戴尸,并且當(dāng)前任務(wù)棧T3的情況為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全部出棧,于是最終T3的情況為AD沾乘。
singleTask3.png
4:假如目前有兩個(gè)任務(wù)棧怜奖,前臺(tái)任務(wù)棧T4的情況為AB,后臺(tái)任務(wù)棧t4里存有CD,假設(shè)CD的啟動(dòng)模式均為singleTask,現(xiàn)在由B去啟動(dòng)D,那么整個(gè)后臺(tái)任務(wù)都會(huì)被切換到前臺(tái)翅阵,這個(gè)時(shí)候整個(gè)棧就變成了ABCD歪玲。
singleTask4.png
5:假如上面的其他條件不變,B啟動(dòng)的是C而不是D,那么整個(gè)棧的情況就變成了ABC,因?yàn)镈在C上面掷匠,會(huì)被清理出棧滥崩。
singleTask5.png
singleInstance
單一實(shí)例模式,整個(gè)手機(jī)操作系統(tǒng)里面只有一個(gè)實(shí)例存在讹语。不同的應(yīng)用去打開(kāi)這個(gè)activity 共享公用的同一個(gè)activity钙皮。他會(huì)運(yùn)行在自己?jiǎn)为?dú),獨(dú)立的任務(wù)棧里面,并且任務(wù)棧里面只有他一個(gè)實(shí)例存在短条。應(yīng)用場(chǎng)景:呼叫來(lái)電界面导匣。這種模式的使用情況比較罕見(jiàn),在Launcher中可能使用茸时」倍ǎ或者你確定你需要使Activity只有一個(gè)實(shí)例。建議謹(jǐn)慎使用可都。
設(shè)置Intent的Flag
系統(tǒng)提供了兩種方式來(lái)設(shè)置一個(gè)Activity的啟動(dòng)模式缓待,除了在AndroidManifest文件中設(shè)置以外,還可以通過(guò)Intent的Flag來(lái)設(shè)置一個(gè)Activity的啟動(dòng)模式渠牲,下面我們?cè)诤?jiǎn)單介紹下一些Flag命斧。
FLAG_ACTIVITY_NEW_TASK
使用一個(gè)新的Task來(lái)啟動(dòng)一個(gè)Activity,但啟動(dòng)的每個(gè)Activity都講在一個(gè)新的Task中嘱兼。該Flag通常使用在從Service中啟動(dòng)Activity的場(chǎng)景国葬,由于Service中并不存在Activity棧,所以使用該Flag來(lái)創(chuàng)建一個(gè)新的Activity棧芹壕,并創(chuàng)建新的Activity實(shí)例汇四。
FLAG_ACTIVITY_SINGLE_TOP
使用singletop模式啟動(dòng)一個(gè)Activity,與指定android:launchMode=“singleTop”效果相同踢涌。
FLAG_ACTIVITY_CLEAR_TOP
使用SingleTask模式來(lái)啟動(dòng)一個(gè)Activity通孽,與指定android:launchMode=“singleTask”效果相同。
FLAG_ACTIVITY_NO_HISTORY
Activity使用這種模式啟動(dòng)Activity睁壁,當(dāng)該Activity啟動(dòng)其他Activity后背苦,該Activity就消失了,不會(huì)保留在Activity棧中潘明。
LaunchMode與StartActivityForResult
我們?cè)陂_(kāi)發(fā)過(guò)程中經(jīng)常會(huì)用到StartActivityForResult方法啟動(dòng)一個(gè)Activity行剂,然后在onActivityResult()方法中可以接收到上個(gè)頁(yè)面的回傳值,但你有可能遇到過(guò)拿不到返回值的情況钳降,那有可能是因?yàn)锳ctivity的LaunchMode設(shè)置為了singleTask厚宰。5.0之后,android的LaunchMode與StartActivityForResult的關(guān)系發(fā)生了一些改變遂填。兩個(gè)Activity铲觉,A和B,現(xiàn)在由A頁(yè)面跳轉(zhuǎn)到B頁(yè)面吓坚,看一下LaunchMode與StartActivityForResult之間的關(guān)系:
after5.0.png
這是為什么呢撵幽?
這是因?yàn)锳ctivityStackSupervisor類中的startActivityUncheckedLocked方法在5.0中進(jìn)行了修改。在5.0之前礁击,當(dāng)啟動(dòng)一個(gè)Activity時(shí)盐杂,系統(tǒng)將首先檢查Activity的launchMode逗载,如果為A頁(yè)面設(shè)置為SingleInstance或者B頁(yè)面設(shè)置為singleTask或者singleInstance,則會(huì)在LaunchFlags中加入FLAG_ACTIVITY_NEW_TASK標(biāo)志,而如果含有FLAG_ACTIVITY_NEW_TASK標(biāo)志的話况褪,onActivityResult將會(huì)立即接收到一個(gè)cancle的信息撕贞,而5.0之后這個(gè)方法做了修改,修改之后即便啟動(dòng)的頁(yè)面設(shè)置launchMode為singleTask或singleInstance测垛,onActivityResult依舊可以正常工作捏膨,也就是說(shuō)無(wú)論設(shè)置哪種啟動(dòng)方式,StartActivityForResult和onActivityResult()這一組合都是有效的食侮。所以如果你目前正好基于5.0做相關(guān)開(kāi)發(fā)号涯,不要忘了向下兼容,這里有個(gè)坑請(qǐng)注意避讓锯七。