Android進(jìn)階之Activity啟動(dòng)模式和應(yīng)用場(chǎng)景詳解
本文轉(zhuǎn)載自微信公眾號(hào)「Android開(kāi)發(fā)編程」,作者Android開(kāi)發(fā)編程。轉(zhuǎn)載本文請(qǐng)聯(lián)系A(chǔ)ndroid開(kāi)發(fā)編程公眾號(hào)。
前言:
Activity 作為 Android 四大組件之一,幾乎是被接觸得最多的;Android對(duì)Activity的管理,Android采用Task來(lái)管理多個(gè)Activity,當(dāng)我們啟動(dòng)一個(gè)應(yīng)用時(shí),Android就會(huì)為之創(chuàng)建一個(gè)Task,然后啟動(dòng)這個(gè)應(yīng)用的入口Activity;
在開(kāi)發(fā)實(shí)際項(xiàng)目中會(huì)包含著多個(gè)Activity,系統(tǒng)中使用任務(wù)棧來(lái)存儲(chǔ)創(chuàng)建的Activity實(shí)例,任務(wù)棧是一種“后進(jìn)先出”的棧結(jié)構(gòu)。舉個(gè)栗子,若我們多次啟動(dòng)同一個(gè)Activity,系統(tǒng)會(huì)創(chuàng)建多個(gè)實(shí)例依次放入任務(wù)棧中,當(dāng)按back鍵返回時(shí),每按一次,一個(gè)Activity出棧,直到棧空為止,當(dāng)棧中;
無(wú)任何Activity,系統(tǒng)就會(huì)回收此任務(wù)棧;
因此在Android基礎(chǔ)中,Activity的啟動(dòng)模式非常重要;
本文將全面詳細(xì)介紹 Activity的啟動(dòng)模式
一、任務(wù)和任務(wù)棧詳解
1、Android中任務(wù)詳解
①任務(wù)是指在執(zhí)行特定作業(yè)時(shí)與用戶交互的一系列 Activity。這些 Activity 按照各自的打開(kāi)順序排列在堆棧(即返回棧)中。設(shè)備主屏幕是大多數(shù)任務(wù)的起點(diǎn)。當(dāng)用戶觸摸應(yīng)用啟動(dòng)器中的圖標(biāo)(或主屏幕上的快捷方式)時(shí),該應(yīng)用的任務(wù)將出現(xiàn)在前臺(tái)。如果應(yīng)用不存在任務(wù)(應(yīng)用最近未曾使用),則會(huì)創(chuàng)建一個(gè)新任務(wù),并且該應(yīng)用的“主”Activity 將作為堆棧中的根 Activity 打開(kāi);
②當(dāng)前 Activity 啟動(dòng)另一個(gè) Activity 時(shí),該新 Activity 會(huì)被推送到堆棧頂部,成為焦點(diǎn)所在。前一個(gè) Activity 仍保留在堆棧中,但是處于停止?fàn)顟B(tài)。Activity 停止時(shí),系統(tǒng)會(huì)保持其用戶界面的當(dāng)前狀態(tài)。用戶按“返回”按鈕時(shí),當(dāng)前 Activity 會(huì)從堆棧頂部彈出(Activity 被銷毀),而前一個(gè) Activity 恢復(fù)執(zhí)行(恢復(fù)其 UI 的前一狀態(tài))。堆棧中的 Activity 永遠(yuǎn)不會(huì)重新排列,僅推入和彈出堆棧:由當(dāng)前 Activity 啟動(dòng)時(shí)推入堆棧;用戶使用“返回”按鈕退出時(shí)彈出堆棧。因此,返回棧以“后進(jìn)先出”對(duì)象結(jié)構(gòu)運(yùn)行;
③任務(wù)是一個(gè)有機(jī)整體,當(dāng)用戶開(kāi)始新任務(wù)或通過(guò)“主頁(yè)”按鈕轉(zhuǎn)到主屏幕時(shí),可以移動(dòng)到“后臺(tái)”。盡管在后臺(tái)時(shí),該任務(wù)中的所有 Activity 全部停止,但是任務(wù)的返回棧仍舊不變,也就是說(shuō),當(dāng)另一個(gè)任務(wù)發(fā)生時(shí),該任務(wù)僅僅失去焦點(diǎn)而已。然后,任務(wù)可以返回到“前臺(tái)”,用戶就能夠回到離開(kāi)時(shí)的狀態(tài);
④由于返回棧中的 Activity 永遠(yuǎn)不會(huì)重新排列,因此如果應(yīng)用允許用戶從多個(gè) Activity 中啟動(dòng)特定 Activity,則會(huì)創(chuàng)建該 Activity 的新實(shí)例并推入堆棧中(而不是將 Activity 的任一先前實(shí)例置于頂部)。因此,應(yīng)用中的一個(gè) Activity 可能會(huì)多次實(shí)例化(即使 Activity 來(lái)自不同的任務(wù))。
2、任務(wù)棧
(1)程序打開(kāi)時(shí)就創(chuàng)建了一個(gè)任務(wù)棧, 用于存儲(chǔ)當(dāng)前程序的activity,所有的activity屬于一個(gè)任務(wù)棧。
(2)一個(gè)任務(wù)棧包含了一個(gè)activity的集合, 去有序的選擇哪一個(gè)activity和用戶進(jìn)行交互:只有在任務(wù)棧棧頂?shù)腶ctivity才可以跟用戶進(jìn)行交互。
(3)任務(wù)棧可以移動(dòng)到后臺(tái), 并且保留了每一個(gè)activity的狀態(tài). 并且有序的給用戶列出它們的任務(wù), 而且還不丟失它們狀態(tài)信息。
(4)退出應(yīng)用程序時(shí):當(dāng)把所有的任務(wù)棧中所有的activity清除出棧時(shí),任務(wù)棧會(huì)被銷毀,程序退出。
(5)每開(kāi)啟一次頁(yè)面都會(huì)在任務(wù)棧中添加一個(gè)Activity,而只有任務(wù)棧中的Activity全部清除出棧時(shí),任務(wù)棧被銷毀,程序才會(huì)退出,這樣就造成了用,戶體驗(yàn)差, 需要點(diǎn)擊多次返回才可以把程序退出了。
(6)每開(kāi)啟一次頁(yè)面都會(huì)在任務(wù)棧中添加一個(gè)Activity還會(huì)造成數(shù)據(jù)冗余, 重復(fù)數(shù)據(jù)太多, 會(huì)導(dǎo)致內(nèi)存溢出的問(wèn)題(OOM)。
為了解決任務(wù)棧的缺點(diǎn),我們引入了啟動(dòng)模式。
啟動(dòng)模式(launchMode)在多個(gè)Activity跳轉(zhuǎn)的過(guò)程中扮演著重要的角色,它可以決定是否生成新的Activity實(shí)例,是否重用已存在的Activity實(shí)例,是否和其他Activity實(shí)例公用一個(gè)task里;
Activity 中有個(gè)啟動(dòng)模式的概念,分別是 standard、singleTop、singleTask 以及 singleinstance。
二、啟動(dòng)模式詳解
1、standard
standard 是標(biāo)準(zhǔn)啟動(dòng)模式,當(dāng)我們沒(méi)有指定 Activity 的啟動(dòng)模式時(shí),默認(rèn)就是這種模式。在 standard 模式下,每次啟動(dòng)一個(gè) Activity 都會(huì)創(chuàng)建一個(gè)新的實(shí)例,它的 onCreate、onStart 以及 onResume均會(huì)被調(diào)用。這個(gè)新創(chuàng)建的 Activity將會(huì)放在啟動(dòng)它的 Activity 所在的任務(wù)棧的棧頂。
比如 Activity A 在棧 S ,它啟動(dòng)了 Activity B(standard 模式),那么 B 將會(huì)進(jìn)入 A 所在的棧 S。
如果在沒(méi)有任務(wù)棧的情況下啟動(dòng) standard 模式的 Activity,比如在 Service 中,此時(shí)新的 Activity 沒(méi)有任務(wù)??扇?,會(huì)出現(xiàn)異常:
- Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
 
此時(shí)應(yīng)該為這個(gè) Activity 指定 FLAG_ACTIVITY_NEW_TASK,這樣就會(huì)新建一個(gè)任務(wù)棧。
2、singleTop
singleTop 是棧頂復(fù)用模式。在這種模式下,如果新啟動(dòng)的 Activity 已經(jīng)在任務(wù)棧的棧頂了,那么就不會(huì)重新創(chuàng)建新的實(shí)例,而是調(diào)用這個(gè) Activity 的 onPause、onNewIntent 以及 onResume 方法。如果新啟動(dòng)的 Activity 不是位于棧頂,那么還是會(huì)重新創(chuàng)建。
比如現(xiàn)在棧內(nèi)情況是 ABCD 四個(gè)Activity,A 位于棧底,D 位于棧頂。如果 D 的啟動(dòng)模式為 singleTop,那么不會(huì)再次創(chuàng)建 D 的實(shí)例,棧內(nèi)依然是 ABCD。
如果上面的 D 為 standard 啟動(dòng)模式,那么棧內(nèi)將變?yōu)?ABCDD。
3、singleTask
singleTask 是棧內(nèi)復(fù)用模式。這是最復(fù)雜的一種模式,因?yàn)樗赡苌婕暗蕉鄠€(gè)棧。當(dāng)一個(gè)具有 singleTask 模式的 Activity 啟動(dòng)后,比如 Activity A,系統(tǒng)會(huì)首先尋找是否存在所需的任務(wù)棧,如果不存在,就重新創(chuàng)建一個(gè)任務(wù)棧,然后創(chuàng)建 A 的實(shí)例后把 A 放入到棧中。如果存在 A 所需要的任務(wù)棧,這時(shí)要看 A 是否在棧中有實(shí)例存在,如果有,那么系統(tǒng)就會(huì)把它調(diào)到棧頂并且調(diào)用它的 onNewIntent 方法,如果不存在,就創(chuàng)建 A 的實(shí)例并把 A 壓入棧中。這里所說(shuō)的 A 所需要的任務(wù)棧是什么意思呢?其實(shí) Activity 是可以指定自己想要的任務(wù)棧的名字的,通過(guò)一個(gè)參數(shù):TaskAffinity,默認(rèn)情況下,所有的 Activity 所需要的任務(wù)棧的名字為應(yīng)用的包名。
如果任務(wù)棧 S1 中的情況為 ABC,這個(gè)時(shí)候 Activity D 以 singleTask 模式請(qǐng)求啟動(dòng),它需要的任務(wù)棧為 S2,由于 S2 和 D 的實(shí)例均不存在,所以系統(tǒng)就會(huì)先創(chuàng)建任務(wù)棧 S2,然后在創(chuàng)建 D 的實(shí)例并將其入棧到 S2
如果上面 D 所需的任務(wù)棧為 S1,那么因?yàn)?S1 已經(jīng)存在,所以系統(tǒng)直接創(chuàng)建 D 的實(shí)例并且入棧到 S1。
如果 D 所需的任務(wù)棧為 S1,但是 S1 中的情況為 ADBC,此時(shí) D 不會(huì)重新創(chuàng)建,而是把 D 切換到棧頂并調(diào)用 onNewIntent 方法。那 B 和 C 怎么辦?它們會(huì)全部出棧,相當(dāng)于 clearTop 效果。
4、singleInstance
singleInstance 是單實(shí)例模式。這種模式是 singleTask 的加強(qiáng)版,它除了具有 singleTask 的所有特性外,還加強(qiáng)了一點(diǎn),那就是此種模式的 Activity 只能單獨(dú)位于一個(gè)任務(wù)棧中。
比如 Activity A 是 singleInstance 模式,當(dāng) A 啟動(dòng)后,系統(tǒng)會(huì)創(chuàng)建一個(gè)新的任務(wù)棧,然后 A 獨(dú)自在這個(gè)新的任務(wù)棧中,由于棧內(nèi)復(fù)用的特性,后續(xù)的請(qǐng)求均不會(huì)創(chuàng)建新的 Activity,除非這個(gè)棧被銷毀了;
三、啟動(dòng)模式設(shè)置詳解
啟動(dòng)模式有2種設(shè)置方式:在AndroidMainifest設(shè)置、通過(guò)Intent設(shè)置標(biāo)志位。
1、在AndroidMainifest的Activity配置進(jìn)行設(shè)置
- <activity
 - android:launchMode="啟動(dòng)模式"
 - //屬性
 - //standard:標(biāo)準(zhǔn)模式
 - //singleTop:棧頂復(fù)用模式
 - //singleTask:棧內(nèi)復(fù)用模式
 - //singleInstance:?jiǎn)卫J?nbsp;
 - //如不設(shè)置,Activity的啟動(dòng)模式默認(rèn)為**標(biāo)準(zhǔn)模式(standard)**
 - </activity>
 
2、通過(guò)Intent設(shè)置標(biāo)志位
- Intent inten = new Intent (ActivityA.this,ActivityB.class);
 - intent.addFlags(Intent,FLAG_ACTIVITY_NEW_TASK);
 - startActivity(intent);
 
- FLAG_ACTIVITY_SINGLE_TOP:指定啟動(dòng)模式為棧頂復(fù)用模式(SingleTop)
 - FLAG_ACTIVITY_NEW_TASK:指定啟動(dòng)模式為棧內(nèi)復(fù)用模式(SingleTask)
 - FLAG_ACTIVITY_CLEAR_TOP:所有位于其上層的Activity都要移除,SingleTask模式默認(rèn)具有此標(biāo)記效果;
 - FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有該標(biāo)記的Activity不會(huì)出現(xiàn)在歷史Activity的列表中,即無(wú)法通過(guò)歷史列表回到該Activity上;
 
3、二者區(qū)別
Intent設(shè)置方式的優(yōu)先級(jí) > Manifest設(shè)置方式,即 以前者為準(zhǔn);
Manifest設(shè)置方式無(wú)法設(shè)定 FLAG_ACTIVITY_CLEAR_TOP;Intent設(shè)置方式 無(wú)法設(shè)置單例模式(SingleInstance);
四、啟動(dòng)模式的實(shí)際應(yīng)用場(chǎng)景
1. SingleTask模式的運(yùn)用場(chǎng)景
最常見(jiàn)的應(yīng)用場(chǎng)景就是保持我們應(yīng)用開(kāi)啟后僅僅有一個(gè)Activity的實(shí)例。最典型的樣例就是應(yīng)用中展示的主頁(yè)(Home頁(yè))。
假設(shè)用戶在主頁(yè)跳轉(zhuǎn)到其他頁(yè)面,運(yùn)行多次操作后想返回到主頁(yè),假設(shè)不使用SingleTask模式,在點(diǎn)擊返回的過(guò)程中會(huì)多次看到主頁(yè),這明顯就是設(shè)計(jì)不合理了。
2. SingleTop模式的運(yùn)用場(chǎng)景
假設(shè)你在當(dāng)前的Activity中又要啟動(dòng)同類型的Activity,此時(shí)建議將此類型Activity的啟動(dòng)模式指定為SingleTop,能夠降低Activity的創(chuàng)建,節(jié)省內(nèi)存!
3.SingleInstance模式的運(yùn)用場(chǎng)景
SingleInstance是activity啟動(dòng)的一種模式,一般做應(yīng)用層開(kāi)發(fā)很少用到,我一般用到的app定時(shí)提醒會(huì)用到這個(gè)模式吧。這個(gè)模式使用起來(lái)有很多坑,假設(shè)有activityA,activityB,activityC這三個(gè)activity,我們將activityB設(shè)置為SingleInstance
第一種情況
A開(kāi)啟B,B開(kāi)啟C,如果finish activityC,那么activityA會(huì)顯示而不是我們想要的activityB,這是因?yàn)閍ctivityB和activityA、activityC所處的棧不同,C關(guān)閉了,就要顯示C所處棧的下一個(gè)activity,解決這個(gè)問(wèn)題辦法很多,我自己用的方法是通過(guò)記錄開(kāi)啟activity,在被關(guān)閉的activity的finish方法中重新開(kāi)啟activityB。
第二種情況
A開(kāi)啟B,然后按home鍵,再?gòu)淖竺纥c(diǎn)開(kāi)應(yīng)用,顯示的是A,這是因?yàn)閘aunch啟動(dòng)我們應(yīng)用的時(shí)候 會(huì)從默認(rèn)的棧找到棧頂?shù)腶ctivity顯示,這個(gè)解決辦法的思路跟第一種差不多,也就不獻(xiàn)丑了
第三種情況
A開(kāi)啟C,C開(kāi)啟B,B開(kāi)啟A,結(jié)果顯示的是C,這還是兩個(gè)棧造成的,B開(kāi)啟A的時(shí)候,其實(shí)是到達(dá)A所處的棧,棧頂是C,所以就顯示C了,解決辦法是用flag把默認(rèn)棧activity清理了,重新開(kāi)啟A,或者回退到C時(shí)再開(kāi)啟A。
三種情況的解決方法都是基于頁(yè)面少的情況,如果頁(yè)面多了會(huì)產(chǎn)生更多的問(wèn)題
為了必避免這個(gè)問(wèn)題,最好不用在中間層使用SingleInstance
TIPS: (1)如果想讓C和B同一個(gè)棧,那就使用taskinfinity,給他倆設(shè)置同樣的棧名
(2)onActivityResult不能與SingleInstance不能一起使用,因?yàn)椴煌瑮?/p>
4、standard 運(yùn)用場(chǎng)景
Activity 的啟動(dòng)默認(rèn)就是這種模式。在 standard 模式下,每次啟動(dòng)一個(gè) Activity 都會(huì)創(chuàng)建一個(gè)新的實(shí)例;
在正常應(yīng)用中正常打開(kāi)和關(guān)閉頁(yè)面就可以了,退出整個(gè)app就關(guān)閉所有的頁(yè)面
5、Activity時(shí)的生命周期不同
由于當(dāng)一個(gè)Activity設(shè)置了SingleTop或者SingleTask模式或者SingleInstance模式后,跳轉(zhuǎn)此Activity出現(xiàn)復(fù)用原有Activity的情況時(shí),此Activity的onCreate方法將不會(huì)再次運(yùn)行。onCreate方法僅僅會(huì)在第一次創(chuàng)建Activity時(shí)被運(yùn)行。
而一般onCreate方法中會(huì)進(jìn)行該頁(yè)面的數(shù)據(jù)初始化、UI初始化,假設(shè)頁(yè)面的展示數(shù)據(jù)無(wú)關(guān)頁(yè)面跳轉(zhuǎn)傳遞的參數(shù),則不必操心此問(wèn)題,若頁(yè)面展示的數(shù)據(jù)就是通過(guò)getInten() 方法來(lái)獲取,那么問(wèn)題就會(huì)出現(xiàn):getInten()獲取的一直都是老數(shù)據(jù),根本無(wú)法接收跳轉(zhuǎn)時(shí)傳送的新數(shù)據(jù)!
這時(shí)我們須要另外一個(gè)回調(diào) onNewIntent(Intent intent)方法。此方法會(huì)傳入最新的intent,這樣我們就能夠解決上述問(wèn)題。這里建議的方法是又一次去setIntent。然后又一次去初始化數(shù)據(jù)和UI
/** 復(fù)用Activity時(shí)的生命周期回調(diào)*/
- @Override
 - protected void onNewIntent(Intent intent) {
 - super.onNewIntent(intent);
 - setIntent(intent);
 - initData();
 - initView();
 - }
 
6、實(shí)際中的棧管理類
管理Activity的類,一般在BaseActivity會(huì)調(diào)用這個(gè)類,然后所有的Activity繼承BaseActivity,這樣管理好整個(gè)項(xiàng)目的Activity
- /**
 - * activity堆棧管理
 - */
 - public class ActivityStackManager {
 - private static ActivityStackManager mInstance;
 - private static Stack<Activity> mActivityStack;
 - public static ActivityStackManager getInstance() {
 - if (null == mInstance) {
 - mInstance = new ActivityStackManager();
 - }
 - return mInstance;
 - }
 - private ActivityStackManager() {
 - mActivityStack = new Stack<Activity>();
 - }
 - /**
 - * 入棧
 - *
 - * @param activity
 - */
 - public void addActivity(Activity activity) {
 - mActivityStack.push(activity);
 - }
 - /**
 - * 出棧
 - *
 - * @param activity
 - */
 - public void removeActivity(Activity activity) {
 - mActivityStack.remove(activity);
 - }
 - /**
 - * 徹底退出
 - */
 - public void finishAllActivity() {
 - Activity activity;
 - while (!mActivityStack.empty()) {
 - activity = mActivityStack.pop();
 - if (activity != null) {
 - activity.finish();
 - }
 - }
 - }
 - /**
 - * 結(jié)束指定類名的Activity
 - *
 - * @param cls
 - */
 - public void finishActivity(Class<?> cls) {
 - for (Activity activity : mActivityStack) {
 - if (activity.getClass().equals(cls)) {
 - finishActivity(activity);
 - }
 - }
 - }
 - /**
 - * 查找棧中是否存在指定的activity
 - *
 - * @param cls
 - * @return
 - */
 - public boolean checkActivity(Class<?> cls) {
 - for (Activity activity : mActivityStack) {
 - if (activity.getClass().equals(cls)) {
 - return true;
 - }
 - }
 - return false;
 - }
 - /**
 - * 結(jié)束指定的Activity
 - *
 - * @param activity
 - */
 - public void finishActivity(Activity activity) {
 - if (activity != null) {
 - mActivityStack.remove(activity);
 - activity.finish();
 - activity = null;
 - }
 - }
 - /**
 - * finish指定的activity之上所有的activity
 - *
 - * @param actCls
 - * @param isIncludeSelf
 - * @return
 - */
 - public boolean finishToActivity(Class<? extends Activity> actCls, boolean isIncludeSelf) {
 - List<Activity> buf = new ArrayList<Activity>();
 - int size = mActivityStack.size();
 - Activity activity = null;
 - for (int i = size - 1; i >= 0; i--) {
 - activity = mActivityStack.get(i);
 - if (activity.getClass().isAssignableFrom(actCls)) {
 - for (Activity a : buf) {
 - a.finish();
 - }
 - return true;
 - } else if (i == size - 1 && isIncludeSelf) {
 - buf.add(activity);
 - } else if (i != size - 1) {
 - buf.add(activity);
 - }
 - }
 - return false;
 - }}
 
總結(jié)
1、以上就是Activity 的啟動(dòng)模式和應(yīng)用場(chǎng)景總結(jié),除了 singleTask 稍微有點(diǎn)復(fù)雜,其它都很好理解
2、啟動(dòng)模式事實(shí)上是實(shí)際應(yīng)用中必須會(huì)的知識(shí)點(diǎn),你不去使用而僅僅是學(xué)習(xí)并不是能夠掌握到精髓,僅僅有真正去使用才會(huì)將這些變成你自己的;




















 
 
 






 
 
 
 