1. 程式人生 > >Android基礎之Activity四種啟動模式和task相關

Android基礎之Activity四種啟動模式和task相關

1 啟動模式介紹

啟動模式簡單地說就是Activity啟動時的策略,在AndroidManifest.xml中的標籤的android:launchMode屬性設定; 啟動模式有4種,分別為standard、singleTop、singleTask、singleInstance;
  Activity的管理方式:任務棧。任務棧採用的結構: “後進先出” 的棧結構。每按一次Back鍵,就有一個Activity出棧。
這裡寫圖片描述
圖片來源於:Android基礎:最易懂的Activity啟動模式詳解

1.1 標準模式–standard

這裡寫圖片描述
  若我意圖開啟的順序為B1->B2->B2,則實際開啟的順序為B1->B2->B2

1.2 棧頂複用模式–singleTop

這裡寫圖片描述
  若我意圖開啟的順序為:B1->B2->B2,則實際開啟的順序為:B1->B2(後一次意圖開啟B2,實際只調用了前一個的onNewIntent方法)
若我意圖開啟的順序為:B1->B2->B1->B2,則實際開啟的順序為:B1->B2->B1->B2。

1.3 棧內複用模式–singleTask

singleTask:如果要啟用的那個Activity在任務棧中存在該例項,則不需要建立,只需要把此Activity放入棧頂,並把該Activity以上的Activity例項都pop(onDestroy())
這裡寫圖片描述


  若我的應用程式中有三個Activity,C1,C2,C3,三個Activity可互相啟動,其中C2為singleTask模式,如下:

  • C1->C2 ----------> C1->C2
  • C1->C2->C3 ----------> C1->C2->C3
  • C1->C2->C3->C2 ----------> C1->C2
  • C1->C2->C3->C2->C3->C1----------> C1->C2->C3->C1
  • C1->C2->C3->C2->C3->C1-C2----------> C1->C2

1.4 單例模式–singleInstance

在一個新棧中建立該Activity的例項,並讓多個應用共享該棧中的該Activity例項。一旦該模式的Activity例項已經存在於某個棧中,任何應用再啟用該Activity時都會重用該棧中的例項( 會呼叫例項的 onNewIntent() )。其效果相當於多個應用共享一個應用,不管誰啟用該 Activity 都會進入同一個應用中。
這裡寫圖片描述
這裡寫圖片描述
  可看,SecondActivity的Task id不同於FirstActivity和ThirdActivity。這說明SecondActivity確實是存放在一個單獨的返回棧裡的,而且這個棧中只有SecondActivity這一個活動。
  按下Back鍵進行返回,發現ThirdActivity竟然直接返回到了FirstActivity,再按下Back鍵又會返回到SecondActivity,再按下Back鍵才會退出程式,這是為什麼呢?
  由於FirstActivity和ThirdActivity是存放在同一個返回棧裡的,當在ThirdActivity的介面按下Back鍵,ThirdActivity會從返回棧中出棧,那麼FirstActivity就成為了棧頂活動顯示在介面上,因此也就出現了從ThirdActivity直接返回到FirstActivity的情況。然後在FirstActivity介面再次按下Back鍵,這時當前的返回棧已經空了,於是就顯示另一個返回棧的棧頂活動,即SecondActivity。最後再次按下Back鍵,這時所有返回棧都已經空了,也就自然退出了程式。
這裡寫圖片描述

2 啟動模式應用場景

2.1 標準模式–standard

普通模式:若我有一個Activity名為A1, 上面有一個按鈕可跳轉到A1。那麼如果我點選按鈕,便會新啟一個Activity A1疊在剛才的A1之上,再點選又會再新啟一個在它之上……。點back鍵會依照棧順序依次退出。

2.2 棧頂複用模式–singleTop

singleTop適合**接收訊息後顯示的頁面,(例如QQ接收倒訊息後彈出Activity,如果10條訊息,總不能一次彈10個Activity,只要複用棧頂顯示的頁面)。**例如:某個新聞客戶端的新聞內容頁面,如果收到10個新聞推送,每次啟用同一個新聞內容頁面,返回時立刻返回前一個頁面。

2.3 棧內複用模式–singleTask

singleTask適合**作為程式入口點,例如:瀏覽器的首頁導航。**多個應用程式啟動瀏覽器的首頁導航,只會啟動一次,其它情況都會走onNewIntent,並且會清空主介面上面的其他頁面。

2.4 單例模式–singleInstance

瀏覽器BrowserActivity很耗記憶體,很多app都會要呼叫它,這樣就可以把該Activity設定成單例模式。
  singleInstance應用場景:鬧鈴的響鈴介面。 你以前設定了一個鬧鈴:上午6點。在上午5點58分,你啟動了鬧鈴設定介面,並按 Home 鍵回桌面;在上午5點59分時,你在微信和朋友聊天;在6點時,鬧鈴響了,並且彈出了一個對話方塊形式的 Activity(AlarmAlertActivity) 提示你到6點了(這個 Activity 就是以 SingleInstance 載入模式開啟的),你按返回鍵,回到的是微信的聊天介面,這是因為 AlarmAlertActivity 所在的 Task 的棧只有他一個元素, 因此退出之後這個 Task 的棧空了。如果是以 SingleTask 開啟 AlarmAlertActivity,那麼當鬧鈴響了的時候,按返回鍵應該進入鬧鈴設定介面。
這裡寫圖片描述

3 taskAffinity(親和力)

擁有相同affinity的多個Activity理論同屬於一個task,task自身的affinity決定於根Activity的affinity值。先找到這個task,沒有再建立了一個task,這個task的affinity就是B的MainActivityB預設的affinity,由於B的MainActivityB的affinity是從Application繼承而來,所以當appA啟動時會直接找到這個task,而不是建立新的task。
  affinity在什麼場合應用呢?(1)根據affinity重新為Activity選擇宿主task(與allowTaskReparenting屬性配合工作)(2)啟動一個Activity過程中Intent使用了FLAG_ACTIVITY_NEW_TASK標記,根據affinity查詢或建立一個新的具有對應affinity的task;(3)在其他情況下沒有意義。

4 Intent幾種常見的啟動標誌(flags)

Activity指定啟動模式有兩種,第一種是通過manifest指定模式,第二種是在Intent中設定標誌位為Activity指定啟動模式。區別在於:(1)優先順序上第二種方式的優先順序比第一種要高,同時存在,以第二種為準。(2)限定範圍不同,第一種不能直接為Activity設定FLAG_ACTIVITY_CLEAR_TOP標識,第二種方式無法為Activity指定singleInstance模式。

4.1 FLAG_ACTIVITY_NEW_TASK

當Intent物件包含這個標記時,系統會尋找或建立一個新的task來放置目標Activity,尋找時依據目標Activity的taskAffinity屬性進行匹配,如果找到一個task的taskAffinity與之相同,就將目標Activity壓入此task中;如果查詢無果,則建立一個新的task,並將該task的taskAffinity設定為目標Activity的taskActivity,將目標Activity放置於此task。
  注意,如果同一個應用中Activity的taskAffinity都使用預設值或都設定相同值時,應用內的Activity之間的跳轉使用這個標記是沒有意義的,因為當前應用task就是目標Activity最好的宿主。通過例項進行演示這個特性:
  (1)普通的跳轉:應用A啟動應用B的B1Activity,應用B的啟動頁是SplashBActivity,B1Activity會留在應用A的棧中。所以,A–>B的B1Activity,按home鍵回到桌面,啟動應用B是B的啟動頁面,啟動應用A是B的B1Activity頁面。
  (2)設定Intent.FLAG_ACTIVITY_NEW_TASK標誌跳轉後:應用A啟動應用B的B1Activity,應用B的啟動頁是B的B1Activity頁面,B1Activity轉移到了應用B的棧中。所以,A–>B的B1Activity,按home鍵回到桌面,啟動應用B是它的B1Activity頁面,啟動應用A是A的啟動頁面。

    //在應用A跳轉應用B,
    public void startActivityB(View v){
        Intent intent = new Intent("com.google.test");
        //設定Intent.FLAG_ACTIVITY_NEW_TASK標誌
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
            try {
                startActivity(intent);
            } catch (ActivityNotFoundException e) {
                Log.e("TAG","activity not found for");
            }
        }
    }

(3)同理,taskAffinity的實現例子:在應用B的manifest配置allowTaskReparenting="true"和taskAffinity=“com.example.interviewb”,效果與設定Intent.FLAG_ACTIVITY_NEW_TASK標誌一樣。

<activity
            android:name=".MainActivityB">
            android:allowTaskReparenting="true"
            android:taskAffinity="com.example.interviewb">
            <intent-filter>
                <action android:name="com.google.test" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

4.2 FLAG_ACTIVITY_CLEAR_TOP

清除包含此Activity的Task中位於該Activity例項之上的其他Activity例項。這種行為的 launchMode 屬性沒有對應的值,只能通過程式碼設定。
  (1)已經啟動了四個Activity:A,B和C。在C裡,我們要跳到A。這樣啟動A,就會銷燬B\C,如果A的啟動模式是預設的,則A會銷燬,再啟動一個新的A,A重新執行onCreate–>onStart。

Intent intent = new Intent(this, A.class);   
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  
startActivity(intent);

這裡寫圖片描述
  (2)如果不想重新再建立一個新的A,則A的Manifest.xml配置成android:launchMode="singleTop"或者配合FLAG_ACTIVITY_SINGLE_TOP使用,則A不會銷燬,只銷毀A以上例項,然後A執行onNewIntent–>onReStart–>onStart。利用此原理退出整個程式:啟動到A,在onNewIntent()中銷燬A,參考:採用FLAG_ACTIVITY_CLEAR_TOP退出整個程式(多activity)

intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

4.3 FLAG_ACTIVITY_CLEAR_TASK

此Activity將變成一個新Task中新的最底端的Activity,成為根Activity,所有的之前此Activity例項和包含該例項的Task都會被關閉,這個標識**僅僅和FLAG_ACTIVITY_NEW_TASK聯合起來才有效果,**單獨使用和標準模式的效果。

4.4 FLAG_ACTIVITY_SINGLE_TOP

使用singleTo啟動Activity,使用效果與指定android:launchMode="singleTop"相同。

4.5 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET

如果一個Intent中包含此屬性,則它轉向的那個Activity以及在那個Activity其上的所有Activity都會在task重置時被清除出task。當我們將一個後臺的task重新回到前臺時,系統會在特定情況下為這個動作附帶一個FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記,意味著必要時重置task,這時FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET就會生效。
這裡寫圖片描述
圖片來源於:基礎總結篇之三:Activity的task相關
  這個標記對於應用存在分割點的情況會非常有用。比如我們在應用主介面要選擇一個圖片,然後我們啟動了圖片瀏覽介面,但是把這個應用從後臺恢復到前臺時,為了避免讓使用者感到困惑,我們希望使用者仍然看到主介面,而不是圖片瀏覽介面,這個時候我們就要在轉到圖片瀏覽介面時的Intent中加入此標記。

4.6 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

這個標記在以下情況下會生效:1.啟動Activity時建立新的task來放置Activity例項;2.已存在的task被放置於前臺。系統會根據affinity對指定的task進行重置操作,task會壓入某些Activity例項或移除某些Activity例項。我們結合上面的CLEAR_WHEN_TASK_RESET可以加深理解。

4.7 FLAG_ACTIVITY_NO_HISTORY

使用NO_HISTOR啟動Activity,當該Activity啟動新的Activity後,新的Activity會消失,不會保留在棧中。例如:A–>B,B使用這種模式啟動C,C再啟動D,D在返回時會finish C,返回到B。經過測試:
  (1)A–>B,B使用這種模式啟動C,C–>D,棧中是A–>B–>C–>D,返回是:DBA。
  (2)A使用這種模式啟動B,B–>C,C–>D,棧中是A–>C–>D(離棧頂隔了一個例項,會清除前一個),返回是:DCA。

5 task相關屬性

5.1 android:allowTaskReparenting

這個屬性用來標記一個Activity例項在當前應用退居後臺後,是否能從啟動它的那個task移動到有共同affinity的task,“true”表示可以移動,“false”表示它必須呆在當前應用的task中,預設值為false。例項在5.1中。

5.2 android:alwaysRetainTaskState

這個屬性用來標記應用的task是否保持原來的狀態,“true”表示總是保持,“false”表示不能夠保證,預設為“false”。此屬性只對task的根Activity起作用,其他的Activity都會被忽略。
  預設情況下,如果一個應用在後臺呆的太久例如30分鐘,使用者從主選單再次選擇該應用時,系統就會對該應用的task進行清理,除了根Activity,其他Activity都會被清除出棧,但是如果在根Activity中設定了此屬性之後,使用者再次啟動應用時,仍然可以看到上一次操作的介面。
  這個屬性對於一些應用非常有用,例如Browser應用程式,有很多狀態,比如開啟很多的tab,使用者不想丟失這些狀態,使用這個屬性就極為恰當。

5.3 android:clearTaskOnLaunch

這個屬性用來標記是否從task清除除根Activity之外的所有的Activity,“true”表示清除,“false”表示不清除,預設為“false”。同樣,這個屬性也只對根Activity起作用,其他的Activity都會被忽略。
  如果設定了這個屬性為“true”,每次使用者重新啟動這個應用時,都只會看到根Activity,task中的其他Activity都會被清除出棧。如果我們的應用中引用到了其他應用的Activity,這些Activity設定了allowTaskReparenting屬性為“true”,則它們會被重新宿主到有共同affinity的task中。
這裡寫圖片描述
圖片來源於:基礎總結篇之三:Activity的task相關

5.4 android:finishOnTaskLaunch

這個屬性和android:allowReparenting屬性相似,不同之處在於allowReparenting屬性是重新宿主到有共同affinity的task中,而finishOnTaskLaunch屬性是銷燬例項。如果這個屬性和android:allowReparenting都設定為“true”,則這個屬性勝出。

6 onNewIntent觸發時機

6.1 位於棧頂:activity的launchMode為singleTop 、singleTask或者singleInstance

這裡寫圖片描述

6.2 清理其他Activity,被onRestart:activity的launchMode為singleTask或者singleInstance

這裡寫圖片描述

6.3 注意,設定標誌跳轉並不會觸發。

7 參考部落格