Android進階之Activity啟動模式和應用場景詳解
本文轉載自微信公眾號「Android開發編程」,作者Android開發編程。轉載本文請聯系Android開發編程公眾號。
前言:
Activity 作為 Android 四大組件之一,幾乎是被接觸得最多的;Android對Activity的管理,Android采用Task來管理多個Activity,當我們啟動一個應用時,Android就會為之創建一個Task,然后啟動這個應用的入口Activity;
在開發實際項目中會包含著多個Activity,系統中使用任務棧來存儲創建的Activity實例,任務棧是一種“后進先出”的棧結構。舉個栗子,若我們多次啟動同一個Activity,系統會創建多個實例依次放入任務棧中,當按back鍵返回時,每按一次,一個Activity出棧,直到棧空為止,當棧中;
無任何Activity,系統就會回收此任務棧;
因此在Android基礎中,Activity的啟動模式非常重要;
本文將全面詳細介紹 Activity的啟動模式
一、任務和任務棧詳解
1、Android中任務詳解
①任務是指在執行特定作業時與用戶交互的一系列 Activity。這些 Activity 按照各自的打開順序排列在堆棧(即返回棧)中。設備主屏幕是大多數任務的起點。當用戶觸摸應用啟動器中的圖標(或主屏幕上的快捷方式)時,該應用的任務將出現在前臺。如果應用不存在任務(應用最近未曾使用),則會創建一個新任務,并且該應用的“主”Activity 將作為堆棧中的根 Activity 打開;
②當前 Activity 啟動另一個 Activity 時,該新 Activity 會被推送到堆棧頂部,成為焦點所在。前一個 Activity 仍保留在堆棧中,但是處于停止狀態。Activity 停止時,系統會保持其用戶界面的當前狀態。用戶按“返回”按鈕時,當前 Activity 會從堆棧頂部彈出(Activity 被銷毀),而前一個 Activity 恢復執行(恢復其 UI 的前一狀態)。堆棧中的 Activity 永遠不會重新排列,僅推入和彈出堆棧:由當前 Activity 啟動時推入堆棧;用戶使用“返回”按鈕退出時彈出堆棧。因此,返回棧以“后進先出”對象結構運行;
③任務是一個有機整體,當用戶開始新任務或通過“主頁”按鈕轉到主屏幕時,可以移動到“后臺”。盡管在后臺時,該任務中的所有 Activity 全部停止,但是任務的返回棧仍舊不變,也就是說,當另一個任務發生時,該任務僅僅失去焦點而已。然后,任務可以返回到“前臺”,用戶就能夠回到離開時的狀態;
④由于返回棧中的 Activity 永遠不會重新排列,因此如果應用允許用戶從多個 Activity 中啟動特定 Activity,則會創建該 Activity 的新實例并推入堆棧中(而不是將 Activity 的任一先前實例置于頂部)。因此,應用中的一個 Activity 可能會多次實例化(即使 Activity 來自不同的任務)。
2、任務棧
(1)程序打開時就創建了一個任務棧, 用于存儲當前程序的activity,所有的activity屬于一個任務棧。
(2)一個任務棧包含了一個activity的集合, 去有序的選擇哪一個activity和用戶進行交互:只有在任務棧棧頂的activity才可以跟用戶進行交互。
(3)任務??梢砸苿拥胶笈_, 并且保留了每一個activity的狀態. 并且有序的給用戶列出它們的任務, 而且還不丟失它們狀態信息。
(4)退出應用程序時:當把所有的任務棧中所有的activity清除出棧時,任務棧會被銷毀,程序退出。
(5)每開啟一次頁面都會在任務棧中添加一個Activity,而只有任務棧中的Activity全部清除出棧時,任務棧被銷毀,程序才會退出,這樣就造成了用,戶體驗差, 需要點擊多次返回才可以把程序退出了。
(6)每開啟一次頁面都會在任務棧中添加一個Activity還會造成數據冗余, 重復數據太多, 會導致內存溢出的問題(OOM)。
為了解決任務棧的缺點,我們引入了啟動模式。
啟動模式(launchMode)在多個Activity跳轉的過程中扮演著重要的角色,它可以決定是否生成新的Activity實例,是否重用已存在的Activity實例,是否和其他Activity實例公用一個task里;
Activity 中有個啟動模式的概念,分別是 standard、singleTop、singleTask 以及 singleinstance。
二、啟動模式詳解
1、standard
standard 是標準啟動模式,當我們沒有指定 Activity 的啟動模式時,默認就是這種模式。在 standard 模式下,每次啟動一個 Activity 都會創建一個新的實例,它的 onCreate、onStart 以及 onResume均會被調用。這個新創建的 Activity將會放在啟動它的 Activity 所在的任務棧的棧頂。
比如 Activity A 在棧 S ,它啟動了 Activity B(standard 模式),那么 B 將會進入 A 所在的棧 S。
如果在沒有任務棧的情況下啟動 standard 模式的 Activity,比如在 Service 中,此時新的 Activity 沒有任務棧可入,會出現異常:
- 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?
此時應該為這個 Activity 指定 FLAG_ACTIVITY_NEW_TASK,這樣就會新建一個任務棧。
2、singleTop
singleTop 是棧頂復用模式。在這種模式下,如果新啟動的 Activity 已經在任務棧的棧頂了,那么就不會重新創建新的實例,而是調用這個 Activity 的 onPause、onNewIntent 以及 onResume 方法。如果新啟動的 Activity 不是位于棧頂,那么還是會重新創建。
比如現在棧內情況是 ABCD 四個Activity,A 位于棧底,D 位于棧頂。如果 D 的啟動模式為 singleTop,那么不會再次創建 D 的實例,棧內依然是 ABCD。
如果上面的 D 為 standard 啟動模式,那么棧內將變為 ABCDD。
3、singleTask
singleTask 是棧內復用模式。這是最復雜的一種模式,因為它可能涉及到多個棧。當一個具有 singleTask 模式的 Activity 啟動后,比如 Activity A,系統會首先尋找是否存在所需的任務棧,如果不存在,就重新創建一個任務棧,然后創建 A 的實例后把 A 放入到棧中。如果存在 A 所需要的任務棧,這時要看 A 是否在棧中有實例存在,如果有,那么系統就會把它調到棧頂并且調用它的 onNewIntent 方法,如果不存在,就創建 A 的實例并把 A 壓入棧中。這里所說的 A 所需要的任務棧是什么意思呢?其實 Activity 是可以指定自己想要的任務棧的名字的,通過一個參數:TaskAffinity,默認情況下,所有的 Activity 所需要的任務棧的名字為應用的包名。
如果任務棧 S1 中的情況為 ABC,這個時候 Activity D 以 singleTask 模式請求啟動,它需要的任務棧為 S2,由于 S2 和 D 的實例均不存在,所以系統就會先創建任務棧 S2,然后在創建 D 的實例并將其入棧到 S2
如果上面 D 所需的任務棧為 S1,那么因為 S1 已經存在,所以系統直接創建 D 的實例并且入棧到 S1。
如果 D 所需的任務棧為 S1,但是 S1 中的情況為 ADBC,此時 D 不會重新創建,而是把 D 切換到棧頂并調用 onNewIntent 方法。那 B 和 C 怎么辦?它們會全部出棧,相當于 clearTop 效果。
4、singleInstance
singleInstance 是單實例模式。這種模式是 singleTask 的加強版,它除了具有 singleTask 的所有特性外,還加強了一點,那就是此種模式的 Activity 只能單獨位于一個任務棧中。
比如 Activity A 是 singleInstance 模式,當 A 啟動后,系統會創建一個新的任務棧,然后 A 獨自在這個新的任務棧中,由于棧內復用的特性,后續的請求均不會創建新的 Activity,除非這個棧被銷毀了;
三、啟動模式設置詳解
啟動模式有2種設置方式:在AndroidMainifest設置、通過Intent設置標志位。
1、在AndroidMainifest的Activity配置進行設置
- <activity
- android:launchMode="啟動模式"
- //屬性
- //standard:標準模式
- //singleTop:棧頂復用模式
- //singleTask:棧內復用模式
- //singleInstance:單例模式
- //如不設置,Activity的啟動模式默認為**標準模式(standard)**
- </activity>
2、通過Intent設置標志位
- Intent inten = new Intent (ActivityA.this,ActivityB.class);
- intent.addFlags(Intent,FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- FLAG_ACTIVITY_SINGLE_TOP:指定啟動模式為棧頂復用模式(SingleTop)
- FLAG_ACTIVITY_NEW_TASK:指定啟動模式為棧內復用模式(SingleTask)
- FLAG_ACTIVITY_CLEAR_TOP:所有位于其上層的Activity都要移除,SingleTask模式默認具有此標記效果;
- FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有該標記的Activity不會出現在歷史Activity的列表中,即無法通過歷史列表回到該Activity上;
3、二者區別
Intent設置方式的優先級 > Manifest設置方式,即 以前者為準;
Manifest設置方式無法設定 FLAG_ACTIVITY_CLEAR_TOP;Intent設置方式 無法設置單例模式(SingleInstance);
四、啟動模式的實際應用場景
1. SingleTask模式的運用場景
最常見的應用場景就是保持我們應用開啟后僅僅有一個Activity的實例。最典型的樣例就是應用中展示的主頁(Home頁)。
假設用戶在主頁跳轉到其他頁面,運行多次操作后想返回到主頁,假設不使用SingleTask模式,在點擊返回的過程中會多次看到主頁,這明顯就是設計不合理了。
2. SingleTop模式的運用場景
假設你在當前的Activity中又要啟動同類型的Activity,此時建議將此類型Activity的啟動模式指定為SingleTop,能夠降低Activity的創建,節省內存!
3.SingleInstance模式的運用場景
SingleInstance是activity啟動的一種模式,一般做應用層開發很少用到,我一般用到的app定時提醒會用到這個模式吧。這個模式使用起來有很多坑,假設有activityA,activityB,activityC這三個activity,我們將activityB設置為SingleInstance
第一種情況
A開啟B,B開啟C,如果finish activityC,那么activityA會顯示而不是我們想要的activityB,這是因為activityB和activityA、activityC所處的棧不同,C關閉了,就要顯示C所處棧的下一個activity,解決這個問題辦法很多,我自己用的方法是通過記錄開啟activity,在被關閉的activity的finish方法中重新開啟activityB。
第二種情況
A開啟B,然后按home鍵,再從左面點開應用,顯示的是A,這是因為launch啟動我們應用的時候 會從默認的棧找到棧頂的activity顯示,這個解決辦法的思路跟第一種差不多,也就不獻丑了
第三種情況
A開啟C,C開啟B,B開啟A,結果顯示的是C,這還是兩個棧造成的,B開啟A的時候,其實是到達A所處的棧,棧頂是C,所以就顯示C了,解決辦法是用flag把默認棧activity清理了,重新開啟A,或者回退到C時再開啟A。
三種情況的解決方法都是基于頁面少的情況,如果頁面多了會產生更多的問題
為了必避免這個問題,最好不用在中間層使用SingleInstance
TIPS: (1)如果想讓C和B同一個棧,那就使用taskinfinity,給他倆設置同樣的棧名
(2)onActivityResult不能與SingleInstance不能一起使用,因為不同棧
4、standard 運用場景
Activity 的啟動默認就是這種模式。在 standard 模式下,每次啟動一個 Activity 都會創建一個新的實例;
在正常應用中正常打開和關閉頁面就可以了,退出整個app就關閉所有的頁面
5、Activity時的生命周期不同
由于當一個Activity設置了SingleTop或者SingleTask模式或者SingleInstance模式后,跳轉此Activity出現復用原有Activity的情況時,此Activity的onCreate方法將不會再次運行。onCreate方法僅僅會在第一次創建Activity時被運行。
而一般onCreate方法中會進行該頁面的數據初始化、UI初始化,假設頁面的展示數據無關頁面跳轉傳遞的參數,則不必操心此問題,若頁面展示的數據就是通過getInten() 方法來獲取,那么問題就會出現:getInten()獲取的一直都是老數據,根本無法接收跳轉時傳送的新數據!
這時我們須要另外一個回調 onNewIntent(Intent intent)方法。此方法會傳入最新的intent,這樣我們就能夠解決上述問題。這里建議的方法是又一次去setIntent。然后又一次去初始化數據和UI
/** 復用Activity時的生命周期回調*/
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- setIntent(intent);
- initData();
- initView();
- }
6、實際中的棧管理類
管理Activity的類,一般在BaseActivity會調用這個類,然后所有的Activity繼承BaseActivity,這樣管理好整個項目的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();
- }
- }
- }
- /**
- * 結束指定類名的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;
- }
- /**
- * 結束指定的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;
- }}
總結
1、以上就是Activity 的啟動模式和應用場景總結,除了 singleTask 稍微有點復雜,其它都很好理解
2、啟動模式事實上是實際應用中必須會的知識點,你不去使用而僅僅是學習并不是能夠掌握到精髓,僅僅有真正去使用才會將這些變成你自己的;