衆所周知,Activity有4種啓動模式,分別是:Standard、SingleTop、SingleTask和SingleInstance,它們控制了被啓動Activity的啓動行爲。本文將經過具體案例,詳細分析這幾種模式的差別和使用場景,方便往後查閱。java
在展開具體分析以前,咱們首先要了解下兩個基礎知識:Activity任務棧和android:taskAffinity
屬性。android
Activity任務棧(Task)是一個標準的棧結構,具備「First In Last Out」的特性,用於在ActivityManagerService側管理全部的Activity(AMS經過TaskRecord標識一個任務棧,經過ActivityRecord標識一個Activity)。git
每當咱們打開一個Activity時,就會有一個Activity組件被添加到任務棧,每當咱們經過「back」鍵退出一個Activity時,就會有一個Activity組件從任務棧出棧。任意時刻,只有位於棧頂的Activity才能夠跟用戶進行交互。github
同一時刻,Android系統能夠有多個任務棧;每一個任務棧可能有一個或多個Activity,這些Activity可能來自於同一個應用程序,也可能來自於多個應用程序。另外,同一個Activity可能只有一個實例,也可能有多個實例,並且這些實例既可能位於同一個任務棧,也可能位於不一樣的任務棧。而這些行爲均可以經過Activity啓動模式進行控制。shell
在Android系統的多個任務棧中,只有一個處於前臺,即前臺任務棧,其它的都位於後臺,即後臺任務棧。後臺任務棧中的Activity處於暫停狀態,用戶能夠經過喚起後臺任務棧中的任意Activity,將後臺任務棧切換到前臺。bash
android:taskAffinity
是Activity的一個屬性,表示該Activity指望的任務棧的名稱。默認狀況下,一個應用程序中全部Activity的taskAffinity都是相同的,即應用程序的包名。固然,咱們能夠在配置文件中爲每一個Activity指定不一樣的taskAffinity(只有和已有包名不一樣,纔有意義)。通常狀況下,該屬性主要和SingleTask啓動模式或者android:allowTaskReparenting
屬性結合使用(下面會詳細介紹),在其餘狀況下沒有意義。ide
上面簡要介紹了Activity任務棧和android:taskAffinity屬性。有了這些基礎以後,咱們就能夠詳細介紹Activity的四種啓動模式了。函數
通常狀況下,咱們在AndroidManifest
配置文件中,爲Activity指定啓動模式和taskAffinity,以下所示:ui
<activity
android:name="leon.com.activitylaunchmode.FirstActivity"
android:launchMode="singleTop"
android:taskAffinity="leon.com.activitylaunchmode1"/>
複製代碼
除此以外,咱們也能夠經過設置Intent的某些標誌位來達到相同的效果,關於這些和Activity啓動模式相關的標誌位,咱們會在下篇文章進行介紹。this
Standard
標準模式,也是系統的默認模式。該模式下,每次啓動Activity,都會建立一個新實例,而且將其加入到啓動該Activity的那個Activity所在的任務棧中,因此目標Activity的多個實例能夠位於不一樣的任務棧。例如:ActivityA啓動了標準模式的ActivityB,那麼ActivityB就會在ActivityA所在的任務棧中。
關於標準模式的Activity,有一個很經典的異常: 當咱們經過非Activity的Context(例如: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會進入啓動它的Activity的任務棧中,可是非Activity的Context又沒有任務棧,因此就出錯了。解決方案在異常信息中已經給出了:在Intent中添加FLAG_ACTIVITY_NEW_TASK
標誌位,這樣就會爲目標Activity建立新的任務棧。
OK,下面咱們經過示例驗證下該啓動模式,具體步驟以下所示:
通過上述步驟,最終的任務棧以下所示(adb shell dumpsys activity activities):
經過MainActivity打印出的日誌以下所示:
經過上面的任務棧和日誌可知:每次啓動MainActivity,都建立了新的實例,且每一個實例都在一個任務棧中(任務棧ID都是8191),最終一個任務棧中累積了多個MainActivity的實例。這樣每次回退,都會看到相同的MainActivity。
SingleTop
棧頂複用模式。該模式下,若目標Activity的實例已經存在,可是沒有位於棧頂,那麼仍然會建立新的實例,並添加到任務棧;若目標Activity的實例已經存在,且位於棧頂,那麼就不會建立新的實例,而是複用已有實例,並依次調用目標Activity的onPause -> onNewIntent -> onResume
方法。
OK,下面經過兩個案例詳細分析下SingleTop模式:
案例1,具體步驟以下所示:
通過上述步驟,最終的任務棧以下所示(adb shell dumpsys activity activities):
經過MainActivity打印出的日誌以下所示:
經過上面的任務棧和日誌可知:儘管屢次啓動了MainActivity,但只在第一次的時候建立了實例,後續的屢次啓動,僅僅調用了已有MainActivity實例的onNewIntent
方法。最終任務棧(TaskId爲2134)內只有一個MainActivity實例。
此外,還有一點須要注意: 屢次啓動處於棧頂的SingleTop模式的Activity時,其回調函數的順序是onPause -> onNewIntent -> onResume
。
案例2,具體步驟以下所示:
通過上述步驟,最終的任務棧以下所示(adb shell dumpsys activity activities):
經過MainActivity打印出的日誌以下所示:
經過FirstActivity打印出的日誌以下所示:
經過上面的任務棧和日誌可知:儘管MainActivity的啓動模式爲SingleTop
,可是當它不位於棧頂時,仍然會建立新的實例。最終任務棧中會出現多個MainActivity和FirstActivity,且自始至終,都只有一個任務棧(TaskId爲2068)。
SingleTask
棧內複用模式。該模式是對SingleTop的進一步增強,若Activity實例已經存在,則不論是不是在棧頂,都不會建立新的實例,而是複用已有Activity實例,即清除任務棧中目標Activity之上的全部Activity,使其位於棧頂,同時也會調用其onNewIntent
方法;若Activity實例不存在,系統首先會確認是否有目標Activity指望的任務棧,若是沒有,就首先建立目標Activity指望的任務棧,而後建立目標Activity實例並添加到指望的任務棧中;相反,若存在指望的任務棧,那麼就直接建立目標Activity實例並將其添加到指望的任務棧。
而Activity指望的任務棧名稱就是經過上面介紹的android:taskAffinity
屬性進行設置的。
OK,針對上述狀況,咱們來看兩個具體案例:
案例1,兩個Activity的taskAffinity屬性相同,具體步驟以下所示:
當第一次經過MainActivity啓動FirstActivity時,任務棧以下所示:
可見,由於兩個Activity指望的任務棧相同,所以MainActivity和FirstActivity處於相同的任務棧中(TaskId爲2074)。而後,經過FirstActivity啓動MainActivity時,任務棧以下所示:
最後,再次經過MainActivity啓動FirstActivity時,任務棧以下所示:
可見,並無爲FirstActivity建立新的實例,而是把FirstActivity之上的MainActivity出棧,直接複用已有FirstActivity實例。經過MainActivity打印出的日誌以下所示:
經過FirstActivity打印出的日誌以下所示:
經過上面的任務棧和日誌可知:當兩個Activity的taskAffinity相同時,第一次啓動FirstActivity時,是直接將其實例入棧到MainActivity所在的任務棧(其實MainActivity所在的任務棧就是FirstActivity指望的任務棧);當第二次啓動FirstActivity時,則是直接複用已有FirstActivity實例,同時調用其onNewIntent
方法。
案例2,兩個Activity的taskAffinity屬性不一樣,具體步驟以下所示:
android:taskAffinity
屬性爲leon.com.activitylaunchmode1,而MainActivity的android:taskAffinity
屬性爲默認值,即App包名:leon.com.activitylaunchmode。當第一次經過MainActivity啓動FirstActivity時,任務棧以下所示:
可見,由於兩個Activity指望的任務棧不一樣,所以系統會爲FirstActivity建立新任務棧(TaskId爲2078)。而後,經過FirstActivity啓動MainActivity時,任務棧以下所示:
由於MainActivity的啓動模式爲Standard,因此其Activity實例會入棧到啓動它的FirstActivity所在的任務棧中。最後,再次經過MainActivity啓動FirstActivity時,任務棧以下所示:
可見,並無爲FirstActivity建立新的實例,而是把FirstActivity之上的MainActivity出棧,直接複用已有FirstActivity實例。經過MainActivity打印出的日誌以下所示:
經過FirstActivity打印出的日誌以下所示:
經過上面的任務棧和日誌可知:和案例1的惟一不一樣就是第一次啓動FirstActivity時,系統會爲其建立新的任務棧。
總的來講:SingleTask模式與android:taskAffinity屬性相關。以MainActivity啓動FirstActivity爲例(MainActivity爲Standard模式,FirstActivity爲SingleTask模式):
- 當MainActivity和FirstActivity的taskAffinity屬性相同時:第一次啓動FirstActivity時,並不會啓動新的任務棧,而是直接將FirstActivity添加到MainActivity所在的任務棧;不然,將FirstActivity所在任務棧中位於FirstActivity之上的所有Activity都刪除,直接跳轉到FirstActivity中。
- 當MainActivity和FirstActivity的taskAffinity屬性不一樣時:第一次啓動FirstActivity時,會建立新的任務棧,而後將FirstActivity添加到新的任務棧中;不然,將FirstActivity所在任務棧中位於FirstActivity之上的所有Activity都刪除,直接跳轉到FirstActivity中。
另外,當目標Activity處於棧頂時,啓動SingleTask模式的目標Activity,其回調函數的順序是onPause -> onNewIntent -> onResume
,和SingleTop模式相同。
而當目標Activity存在,可是不位於棧頂時,啓動SingleTask模式的目標Activity,其回調函數的順序是onNewIntent -> onRestart -> onStart -> onResume
。
SingleInstance
單實例模式。該模式是SingleTask的強化,除了具備SingleTask的全部特性外,還強調任意時刻只容許存在惟一的Activity實例,且該Activity實例獨自佔有一個任務棧。即該任務棧只能容納該Activity實例,不能再添加其餘Activity實例到該任務棧,若是該Activity實例已經存在於某個任務棧,則直接跳轉到該任務棧。
OK,下面經過一個案例詳細分析下SingleInstance模式,具體步驟以下所示:
當第一次經過MainActivity啓動FirstActivity時,任務棧以下所示:
可見,此時FirstActivity處於獨立的任務棧中(TaskId爲2087)。而後,經過FirstActivity啓動MainActivity時,任務棧以下所示:
可見,新建立的MainActivity實例不是位於FirstActivity實例所處的任務棧,而是位於以前MainActivity實例所處的任務棧(TaskId爲2086)。這也間接證實了SingleInstance模式的Activity是獨佔一個任務棧的。最後,再次經過MainActivity啓動FirstActivity時,任務棧以下所示:
可見,並無爲FirstActivity建立新的實例,僅僅是把TaskId爲2087的任務棧切換到了前臺。經過MainActivity打印出的日誌以下所示:
經過FirstActivity打印出的日誌以下所示:
經過上面的任務棧和日誌可知:儘管屢次啓動了SingleInstance模式的FirstActivity,但只在第一次的時候建立了Activity實例,而且爲該實例建立了新的任務棧,後續的屢次啓動,僅僅調用了已有FirstActivity實例的onNewIntent
方法,並將其任務棧切換到了前臺。 同時,FirstActivity所處任務棧的TaskId爲2087,MainActivity所處任務棧的TaskId爲2086,MainActivity的實例永遠不可能位於FirstActivity所處的任務棧中,即SingleInstance模式的Activity獨佔一個任務棧。
此外,還有一點須要注意: 當目標Activity實例已經存在時,啓動SingleInstance模式的目標Activity,其回調函數的順序是onNewIntent -> onRestart -> onStart -> onResume
。
上述經過具體案例詳細分析了四種啓動模式,可是還有一些特殊使用場景須要詳細分析下。
假設如今有兩個任務棧:前臺任務棧中包含ActivityA和ActivityB,後臺任務棧中包含ActivityC和ActivityD,且前臺任務棧中Activity的啓動模式均爲Standard,後臺任務棧中Activity的啓動模式均爲SingleTask,兩個任務棧的棧名是不一樣的。
如今有兩種典型的使用場景: 場景1:當經過前臺任務棧中的ActivityB啓動後臺任務棧中ActivityD時,系統會將後臺任務棧會切換到前臺。此時,當用戶經過「back」鍵退出時,整個退出順序應該是ActivityD -> ActivityC -> ActivityB -> ActivityA
。
場景2:當經過前臺任務棧中的ActivityB啓動後臺任務棧中ActivityC時,系統會將後臺任務棧會切換到前臺,同時把ActivityC之上的ActivityD出棧。此時,當用戶經過「back」鍵退出時,整個退出順序應該是ActivityC -> ActivityB -> ActivityA
。
這裏咱們經過具體案例驗證下場景1(場景2比較相似,再也不贅述),具體步驟以下所示:
當完成步驟1~3時,任務棧以下所示:
可知,TaskIdId爲2146的任務棧包含ActivityA和ActivityB,TaskId爲2145的任務棧包含ActivityA和ActivityB。此時,如果直接經過「back」鍵退出,那麼退出順序是ActivityB -> ActivityA
。
當經過ActivityB啓動ActivityD後,任務棧以下所示:
可知,包含ActivityD的任務棧被切換到了前臺,雖然ActivityC和ActivityD被隔開了,可是從TaskId和棧名來看,他們仍是是同一個任務棧,而ActivityA和ActivityB所在的棧則在後面。此時,若經過「back」鍵退出,那麼退出順序是ActivityD -> ActivityC -> ActivityB -> ActivityA
。
android:allowTaskReparenting
主要用於Activity的遷移。當allowTaskReparenting爲true時,表示該Activity能從其餘任務棧遷移到指望的任務棧,當allowTaskReparenting的值爲「false」,表示該Activity不能從其餘任務棧進行遷移,默認爲false。這個比較抽象,咱們看一個具體的案例,具體步驟以下所示:
OK,首先來看下ActivityC的allowTaskReparenting屬性爲true的狀況。 完成上述1~2步驟後的任務棧以下所示:
可見,此時ActivityC和ActivityA處於同一個任務棧。完成第3步後的任務棧以下所示:
可見,此時ActivityC從TaskId爲2166的任務棧遷移到了TaskId爲2167的任務棧中,符合咱們的預期。而後看下ActivityC的allowTaskReparenting屬性爲false的狀況。 完成上述1~2步驟後的任務棧以下所示:
可見,此時ActivityC和ActivityA處於同一個任務棧。完成第3步後的任務棧以下所示:
可見,原來的ActivityC實例並無被遷移,而是系統爲ActivityC建立了新的實例,也符合咱們的預期。先介紹這兩個特殊使用場景,後面會陸續補充...