引言
對Android的啟動模式
還有些印象,現(xiàn)在項(xiàng)目的一個(gè)需求是:登錄頁是一個(gè)登錄選擇頁
玩般,包含了帳號密碼登錄高每、手機(jī)號登錄屿岂、微信第三方登錄幾種選擇,選擇某種登錄方式意味著進(jìn)入某個(gè)登錄頁面
鲸匿,點(diǎn)擊“返回鍵”
能返回到登錄選擇頁
爷怀,某個(gè)注冊/登錄頁面
注冊/登錄成功后當(dāng)前頁面需要銷毀,登錄選擇頁面也需要銷毀带欢。
帶著這樣的需求运授,我們進(jìn)入主題。
Activity的四種啟動模式:
Android的啟動模式(android:launchMode
乔煞,我們在AndroidMainfest
中吁朦,給Activity
添加這個(gè)屬性,就可以設(shè)置啟動模式):
standard(默認(rèn)啟動模式):在默認(rèn)啟動模式下渡贾,無論我們之前創(chuàng)建過這個(gè)Activity逗宜,還是沒有創(chuàng)建過,他都會重新創(chuàng)建一個(gè)剥啤。
singleInstance(全局單一模式):每一個(gè)Activity都存放到一個(gè)新的任務(wù)棧(Task)中锦溪,所以他們都是位于棧頂。
singleTop(棧頂復(fù)用模式)
singleTask(棧內(nèi)復(fù)用模式)
Task
- 一個(gè)后進(jìn)先出的棧結(jié)構(gòu)府怯,用來保存調(diào)度activity
standard (默認(rèn)的啟動模式)
- 默認(rèn)只在當(dāng)前task啟動
- 啟動時(shí)會新建一個(gè)activity實(shí)例放到棧頂
singleTop(棧頂復(fù)用模式)
默認(rèn)只在當(dāng)前task啟動
當(dāng) 當(dāng)前task的棧頂已經(jīng)有一個(gè)相同的Activity刻诊,則不會創(chuàng)建新的activity,而是會調(diào)用棧頂activity的onNewIntent()方法
棧頂不存在相同Activity時(shí)牺丙,會新建一個(gè)activity實(shí)例放到棧頂
singleTask
默認(rèn)可以在其他task啟動则涯,但是到底會不會在其他task啟動需要依賴taskAffinity具體分析
當(dāng) 當(dāng)前task中已經(jīng)存在一個(gè)相同的Activity時(shí),會將task中位于該Activity實(shí)例上方的activity出棧直到該Activity實(shí)例位于棧頂(類似Intent中添加FLAG_ACTIVITY_CLEAR_TOP的效果)
對一個(gè)singleTask的Activity類冲簿,系統(tǒng)中只會存在一個(gè)實(shí)例
singleInstance(獨(dú)享task)
默認(rèn)新建task粟判,并獨(dú)占該task
singleInstance的Activity中啟動的task會在其他task
系統(tǒng)中同樣只會存在一個(gè)實(shí)例
絮叨一下基本概念
一個(gè)應(yīng)用程序
當(dāng)中通常都會包含很多個(gè)Activity
,每個(gè)Activity都是一個(gè)具有特定的功能峦剔,并且可以讓用戶進(jìn)行操作的組件档礁。
另外憨攒,Activity之間可以相互啟動矫俺,當(dāng)前應(yīng)用
的Activity
甚至可以去啟動其他應(yīng)用
的Activity
。比如你的應(yīng)用希望去發(fā)送一封郵件涂臣,你就可以定義一個(gè)具有"send"
動作的Intent
惨险,并且傳入一些數(shù)據(jù)羹幸,如對方郵箱地址、郵件內(nèi)容等辫愉。這樣栅受,如果另外一個(gè)應(yīng)用程序中的某個(gè)Activity聲明自己是可以響應(yīng)這種Intent的,那么這個(gè)Activity就會被打開。當(dāng)郵件發(fā)送之后屏镊,按下返回鍵仍然還是會回到你的應(yīng)用程序當(dāng)中依疼,這讓用戶看起來好像剛才那個(gè)編寫郵件的Activity就是你的應(yīng)用程序當(dāng)中的一部分。所以說闸衫,即使有很多個(gè)Activity分別都是來自于不同應(yīng)用程序的涛贯,Android系統(tǒng)仍然可以將它們無縫地結(jié)合到一起。那這一切是怎么實(shí)現(xiàn)的呢蔚出?這就要講到Activity任務(wù)棧以及Activity啟動模式了弟翘。
任務(wù)棧是什么
任務(wù)棧Task
,是一種用來放置Activity實(shí)例
的容器
骄酗,它是以棧
的形式進(jìn)行盛放稀余,也就是所謂的先進(jìn)后出,主要有2個(gè)基本操作:壓棧
和出棧
趋翻,其所存放的Activity是不支持重新排序的睛琳,只能根據(jù)壓棧和出棧操作更改Activity的順序。
啟動一個(gè)Application
的時(shí)候踏烙,系統(tǒng)會為它默認(rèn)
創(chuàng)建一個(gè)對應(yīng)的Task
师骗,用來放置根Activity
。默認(rèn)啟動Activity
會放在同一個(gè)Task
中讨惩,新啟動的Activity會被壓入啟動它的那個(gè)Activity的棧中辟癌,并且顯示它。當(dāng)用戶按下回退鍵
時(shí)荐捻,這個(gè)Activity就會被彈出棧黍少,按下Home鍵回到桌面,再啟動另一個(gè)應(yīng)用处面,這時(shí)候之前那個(gè)Task就被移到后臺
厂置,成為后臺任務(wù)棧
,而剛啟動的那個(gè)Task就被調(diào)到前臺魂角,成為前臺任務(wù)棧
昵济,Android系統(tǒng)
顯示的就是前臺任務(wù)棧
中的Top實(shí)例
Activity。
任務(wù)棧的作用
以往基于應(yīng)用
(application)的程序開發(fā)中野揪,程序具有明確的邊界访忿,一個(gè)程序就是一個(gè)應(yīng)用,一個(gè)應(yīng)用為了實(shí)現(xiàn)功能可以采用開辟新線程甚至新進(jìn)程來輔助囱挑,但是應(yīng)用與應(yīng)用之間不能復(fù)用資源和功能醉顽。而Android引入了基于組件開發(fā)
的軟件架構(gòu)
沼溜,雖然我們開發(fā)android程序平挑,仍然使用一個(gè)apk工程
一個(gè)Application
的開發(fā)形式,但是對于Aplication的開發(fā)就用到了Activity
、service
等四大組件通熄,其中的每一個(gè)組件唆涝,都是可以被跨應(yīng)用
復(fù)用的,這就是android的神奇之處唇辨。雖然組件可以跨應(yīng)用被調(diào)用廊酣,但是一個(gè)組件所在的進(jìn)程必須是在組件所在的Aplication進(jìn)程中。由于android強(qiáng)化了組件概念赏枚,弱化了Application的概念亡驰,所以在android程序開發(fā)中,A應(yīng)用的A組件想要使用拍照或錄像的功能就可以不用去針對Camera類進(jìn)行開發(fā)饿幅,直接調(diào)用系統(tǒng)自帶的攝像頭應(yīng)用(稱其B應(yīng)用)中的組件(稱其B組件)就可以了凡辱,但是這就引發(fā)了一個(gè)新問題,A組件運(yùn)行在A應(yīng)用中栗恩,B組件運(yùn)行在B應(yīng)用中透乾,自然都不在同一個(gè)進(jìn)程中,那么從B組件中返回的時(shí)候磕秤,如何實(shí)現(xiàn)正確返回到A組件呢乳乌?Task
就是來負(fù)責(zé)實(shí)現(xiàn)這個(gè)功能的,它是從用戶角度
來理解應(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)中的位置客情,來決定該Activity的狀態(tài)。正常情況下癞己,當(dāng)一個(gè)Activity啟動了另一個(gè)Activity的時(shí)候膀斋,新啟動的Activity就會置于任務(wù)棧的頂端,并處于活動狀態(tài)
痹雅,而啟動它的Activity雖然成功身退仰担,但依然保留在任務(wù)棧中,處于停止?fàn)顟B(tài)
绩社,當(dāng)用戶按下返回鍵
或者調(diào)用finish()
方法時(shí)摔蓝,系統(tǒng)會移除頂部Activity,讓后面的Activity恢復(fù)活動狀態(tài)愉耙。當(dāng)然贮尉,世界不可能一直這么“和諧”,可以給Activity設(shè)置一些“特權(quán)”朴沿,來打破這種“和諧”的模式猜谚,這種特權(quán)败砂,就是通過在AndroidManifest
文件中的屬性andorid:launchMode
來設(shè)置或者通過Intent
的flag
來設(shè)置的。
下面就先介紹下Activity的幾種啟動模式魏铅。
圖解Activity的啟動模式
standard
默認(rèn)模式昌犹,可以不用寫配置。在這個(gè)模式下览芳,都會默認(rèn)創(chuàng)建一個(gè)新的實(shí)例斜姥。因此,在這種模式下沧竟,可以有多個(gè)相同的實(shí)例铸敏,也允許多個(gè)相同Activity疊加。應(yīng)用場景:絕大多數(shù)Activity悟泵。
如果以這種方式啟動的Activity被跨進(jìn)程調(diào)用搞坝,在5.0之前新啟動的Activity實(shí)例會放入發(fā)送Intent的Task的棧的頂部,盡管它們屬于不同的程序魁袜,這似乎有點(diǎn)費(fèi)解桩撮,看起來也不是那么合理,所以在5.0之后峰弹,上述情景會創(chuàng)建一個(gè)新的Task店量,新啟動的Activity就會放入剛創(chuàng)建的Task中,這樣就合理的多了鞠呈。
singleTop
棧頂復(fù)用模式
融师,如果要開啟的activity在任務(wù)棧的頂部已經(jīng)存在,就不會創(chuàng)建新的實(shí)例蚁吝,而是調(diào)用 onNewIntent()
方法旱爆。避免棧頂?shù)腶ctivity被重復(fù)的創(chuàng)建。應(yīng)用場景:在通知欄點(diǎn)擊收到的通知窘茁,然后需要啟動一個(gè)Activity怀伦,這個(gè)Activity就可以用singleTop,否則每次點(diǎn)擊都會新建一個(gè)Activity山林。當(dāng)然實(shí)際的開發(fā)過程中房待,測試妹紙沒準(zhǔn)給你提過這樣的bug:某個(gè)場景下連續(xù)快速點(diǎn)擊,啟動了兩個(gè)Activity驼抹。如果這個(gè)時(shí)候待啟動的Activity使用 singleTop模式也是可以避免這個(gè)Bug的桑孩。
同standard
模式,如果是外部程序啟動singleTop的Activity框冀,在Android 5.0之前新創(chuàng)建的Activity會位于調(diào)用者的Task中流椒,5.0及以后會放入新的Task中。
singleTask
棧內(nèi)復(fù)用模式
明也, activity只會在任務(wù)棧里面存在一個(gè)實(shí)例宣虾。如果要激活的activity极谊,在任務(wù)棧里面已經(jīng)存在,就不會創(chuàng)建新的activity安岂,而是復(fù)用這個(gè)已經(jīng)存在的activity,調(diào)用 onNewIntent()
方法帆吻,并且清空
這個(gè)activity任務(wù)棧上面所有的activity域那。應(yīng)用場景:大多數(shù)App的主頁
。對于大部分應(yīng)用猜煮,當(dāng)我們在主界面點(diǎn)擊回退按鈕的時(shí)候都是退出應(yīng)用次员,那么當(dāng)我們第一次進(jìn)入主界面之后,主界面位于棧底王带,以后不管我們打開了多少個(gè)Activity淑蔚,只要我們再次回到主界面,都應(yīng)該使用將主界面Activity上所有的Activity移除的方式來讓主界面Activity處于棧頂愕撰,而不是往棧頂新加一個(gè)主界面Activity的實(shí)例刹衫,通過這種方式能夠保證退出應(yīng)用時(shí)所有的Activity都能銷毀
。
在跨應(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í)候Activity D以singleTask模式請求啟動舍肠,其所需要的任務(wù)棧正是T1搀继,則系統(tǒng)會直接創(chuàng)建D的實(shí)例并將其入棧到T1中。
2:假如D Activity啟動所需要的任務(wù)棧為T2,由于T2和D的實(shí)例均不存在翠语,那么系統(tǒng)會先創(chuàng)建任務(wù)棧T2叽躯,然后再創(chuàng)建D的實(shí)例并將其入棧到T2中。我們可以通過設(shè)置Activity的taskAffinity屬性來模擬這一場景肌括。
<activity
android:name=".SingleTaskActivity"
android:label="singleTask launchMode"
android:launchMode="singleTask"
android:taskAffinity="">
</activity>
3:如果D所需的任務(wù)棧為T3险毁,并且當(dāng)前任務(wù)棧T3的情況為ADBC,根據(jù)棧內(nèi)復(fù)用的原則们童,此時(shí)D不會重新創(chuàng)建畔况,系統(tǒng)會把D切換到棧頂并調(diào)用其onNewIntent()方法,同時(shí)由于singleTask默認(rèn)具有ClearTop的效果慧库,會導(dǎo)致棧內(nèi)所有在D上面的Activity全部出棧跷跪,于是最終T3的情況為AD。
4:假如目前有兩個(gè)任務(wù)棧齐板,前臺任務(wù)棧T4的情況為AB,后臺任務(wù)棧t4里存有CD,假設(shè)CD的啟動模式均為singleTask吵瞻,現(xiàn)在由B去啟動D葛菇,那么整個(gè)后臺任務(wù)都會被切換到前臺,這個(gè)時(shí)候整個(gè)棧就變成了ABCD橡羞。
5:假如上面的其他條件不變眯停,B啟動的是C而不是D,那么整個(gè)棧的情況就變成了ABC,因?yàn)镈在C上面,會被清理出棧卿泽。
singleInstance
單一實(shí)例模式
莺债,整個(gè)手機(jī)操作系統(tǒng)里面只有一個(gè)實(shí)例存在。不同的應(yīng)用去打開這個(gè)activity 共享公用
的同一個(gè)activity签夭。它會運(yùn)行在自己單獨(dú)
齐邦、獨(dú)立
的任務(wù)棧
里面,并且任務(wù)棧里面只有它一個(gè)實(shí)例存在第租。應(yīng)用場景:呼叫來電界面措拇。這種模式的使用情況比較罕見,在Launcher
中可能使用慎宾∝は牛或者你確定你需要使Activity只有一個(gè)實(shí)例。建議謹(jǐn)慎使用趟据。
設(shè)置Intent的Flag
系統(tǒng)提供了兩種方式來設(shè)置一個(gè)Activity的啟動模式汰蜘,除了在AndroidManifest
文件中設(shè)置以外,還可以通過Intent
的Flag
來設(shè)置一個(gè)Activity的啟動模式
之宿,下面我們簡單介紹一些Flag
族操。
- FLAG_ACTIVITY_NEW_TASK
使用一個(gè)新的Task來啟動一個(gè)Activity,但啟動的每個(gè)Activity都將在一個(gè)新的Task中比被。該Flag通常使用在從Service
中啟動Activity
的場景色难,由于Service中并不存在Activity棧
,所以使用該Flag
來創(chuàng)建一個(gè)新的Activity棧等缀,并創(chuàng)建新的Activity實(shí)例枷莉。
- FLAG_ACTIVITY_SINGLE_TOP
使用singletop模式啟動一個(gè)Activity,與指定android:launchMode=“singleTop”效果相同尺迂。
- FLAG_ACTIVITY_CLEAR_TOP
使用SingleTask模式來啟動一個(gè)Activity笤妙,與指定android:launchMode=“singleTask”效果相同。
- FLAG_ACTIVITY_NO_HISTORY
Activity使用這種模式啟動Activity噪裕,當(dāng)該Activity啟動其他Activity后蹲盘,該Activity就消失了,不會保留在Activity棧中膳音。
LaunchMode與StartActivityForResult
我們在開發(fā)過程中經(jīng)常會用到StartActivityForResult
方法啟動一個(gè)Activity召衔,然后在onActivityResult()
方法中可以接收到上個(gè)頁面的回傳值,但你有可能遇到過拿不到返回值的情況祭陷,那有可能是因?yàn)锳ctivity的LaunchMode
設(shè)置為了singleTask
苍凛。5.0之后趣席,android的LaunchMode
與StartActivityForResult
的關(guān)系發(fā)生了一些改變。兩個(gè)Activity醇蝴,A和B宣肚,現(xiàn)在由A頁面跳轉(zhuǎn)到B頁面,看一下LaunchMode與StartActivityForResult之間的關(guān)系:
這是為什么呢悠栓?
這是因?yàn)锳ctivityStackSupervisor類中的startActivityUncheckedLocked
方法在5.0中進(jìn)行了修改霉涨。在5.0之前,當(dāng)啟動一個(gè)Activity時(shí)闸迷,系統(tǒng)將首先檢查Activity的launchMode
,如果為A頁面設(shè)置為SingleInstance或者B頁面設(shè)置為singleTask或者singleInstance,則會在LaunchFlags中加入FLAG_ACTIVITY_NEW_TASK
標(biāo)志俘枫,而如果含有FLAG_ACTIVITY_NEW_TASK標(biāo)志的話腥沽,onActivityResult將會立即接收到一個(gè)cancel
的信息,而5.0之后這個(gè)方法做了修改鸠蚪,修改之后即便啟動的頁面設(shè)置launchMode為singleTask或singleInstance今阳,onActivityResult依舊可以正常工作,也就是說無論設(shè)置哪種啟動方式茅信,StartActivityForResult
和onActivityResult()
這一組合都是有效的盾舌。
所以如果你目前正好基于5.0做相關(guān)開發(fā),不要忘了向下兼容蘸鲸,這里有個(gè)坑請注意避讓妖谴。
實(shí)際需求示例
現(xiàn)在有個(gè)這樣的需求,登錄頁是個(gè)選擇登錄頁酌摇,里面含有帳號密碼登錄方式B膝舅、手機(jī)號登錄/注冊方式C、微信第三方登錄D等幾種選擇窑多,視為activity A仍稀。點(diǎn)擊A中的某個(gè)button實(shí)現(xiàn)activity的跳轉(zhuǎn),比如B埂息,在B中返回能跳到登錄選擇頁技潘。當(dāng)B/C/D成功登錄時(shí),應(yīng)跳轉(zhuǎn)到主頁面MainAcitivity E千康,此時(shí)點(diǎn)返回鍵應(yīng)該是直接跳出應(yīng)用享幽,而不是又跳轉(zhuǎn)會A/B/C/D頁面去。單單是想讓B/C/D頁面消失拾弃,其實(shí)很容易琉闪,在做activity跳轉(zhuǎn)時(shí),finish當(dāng)前頁面即可砸彬。但頁面A是沒有辦法清楚的颠毙,解決方案如下:
Intent intent = new Intent(A.this,B.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
且看看FLAG_ACTIVITY_CLEAR_TASK 的注釋:
/**
* If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
* this flag will cause any existing task that would be associated with the
* activity to be cleared before the activity is started. That is, the activity
* becomes the new root of an otherwise empty task, and any old activities
* are finished. This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}.
*/
public static final int FLAG_ACTIVITY_CLEAR_TASK = 0X00008000;
源碼中明確說明如果在startActivity的時(shí)候傳遞FLAG_ACTIVITY_CLEAR_TASK
這個(gè)標(biāo)志,那么這個(gè)標(biāo)志將會清除之前所有已經(jīng)打開的activity.然后將會變成另外一個(gè)空棧的root
,然后其他的Activitys就都被關(guān)閉了.這個(gè)方法必須跟著{@link #FLAG_ACTIVITY_NEW_TASK
}一起使用.
總結(jié)
實(shí)際開發(fā)過程中如果采用比較合理的Activity啟動模式來做好任務(wù)棧的管理斯入,可以事半功倍。在launchMode的選擇上首先要搞清楚當(dāng)前的Activity的作用蛀蜜,以及實(shí)際使用場景來做出合理選擇刻两。