Task、Back stack、taskAffinity、Activity啓動模式之間的關係

         假設有一個應用程序,它有2個界面即2Activity,當程序第一次啓動時,首先顯示第1個界面Activity 1,然後點擊第1個界面上的一個按鈕啓動到它的第2個界面Activity 2,此時按下返回鍵程序又回到Activity 1,繼續按返回鍵程序就退出到手機主界面,程序的2Activity表現出的是一種後進先出的行爲,可以認爲是有一個棧結構來保存程序依次啓動的每個Activity(事實也如此),當啓動Activity 1時,Activity 1就被存入這個棧中,接着啓動Activity 2時,Activity 2也被存入棧中,正好處於Activity 1的上面即棧頂,當按下返回鍵時Activity 2就從棧頂彈出並被銷燬,這時Activity 1就處於棧頂並顯示其內容,當再按返回鍵時,Activity 1也從棧頂彈出並銷燬了,然後程序就退出到手機的主界面了。這個維護Activity進出的棧結構就叫做Back stack,而棧中的一系列Activity的進出稱爲一個Task,即Task是一個動態的概念,每個Task都有一個與之關聯的Back stack。默認情況下,一個應用程序中的所有Activity都運行在同一個Task管理的Back stack中,因爲默認時所有Activity都具有相同的taskAffinity,這個taskAffinity是一個字符串值,默認值爲程序的包名。在應用程序的AndroidManifest.xml文件中可以單獨爲每個Activity指定別的taskAffinity值(在activity標籤中指定屬性android:taskAffinity即可),但即使各Activity有不同的taskAffinity值,也不意味着它們就一定屬於不同的Task,打個比方,一年級1班組織了一個戶外植樹活動,這個活動稱之爲“1班的植樹Task”,1班的所有同學的taskAffinity值都稱爲“1班”,即使2班的所有同學的taskAffinity值是“2班”,但只要2班某個同學也參加這次植樹活動,那麼這個同學同樣也屬於“1班的植樹Task”,並要遵守1班的任務安排。當然整個1年級也可以組織屬於整個年級的植樹活動“年級的植樹task”,而1班和2班的同學都可以重新指定它們的taskAffinity值爲“一年級”,這樣兩個班的同學都參加了同一次活動並同受年級管理。另外1班還可以把全班同學分爲幾個不同的組,每個組組織每個組自己單獨的活動。在這個例子中,1班和2班代表不同的兩個應用程序,從中可推出,兩個不同應用程序中的Activity可以通過某種方式讓它們運行於同一個Back stack(即同一個task)中;一個應用程序中的部分Activity可以安排到別的應用程序的Back stack中;一個應用程序中的各Activity可以分別安排到不同的Back stack中。

       有了以上關於TaskBack stacktaskAffinity的討論,下面再來講解下關於Activity啓動模式等方面的內容。

每個應用程序通常都有一個主入口點,即在AndroidManifest.xml文件中爲Activity指定了:

         <intent-filter>

                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

         </intent-filter>

那麼在手機主菜單中就列出了這個程序的圖標,當點擊程序圖標時,標識爲主入口點的Activity就被啓動了。我們知道啓動Activity一般用startActivity(Intent intent)函數,很顯然在Launcher的主菜單中執行了這個函數,但還有一點要注意的是,在調用這個函數時,其參數中還設置了一個標誌值Intent.FLAG_ACTIVITY_NEW_TASK,這個值的意思是如果將要被啓動的Activity(應用程序的Activity)與啓動它的ActivityLauncher界面)的taskAffinity值不同時,那麼就新建一個task及相應的Back stack,來維護新啓動程序的各個Activity;而如果taskAffinity值相同時,則新啓動的Activity直接放在啓動它的ActivityLauncher界面)的Back stack中。由於默認情況下每個應用程序中所有ActivitytaskAffinity值都是程序的包名,而各程序都有不同的包名,所以在Launcher中啓動某個程序時這個程序就會新建一個task及對應的Back stack來維護自己的Activity。接下來講講Activity的啓動模式(launchMode)。

        在程序的AndroidManifest.xml文件的每個activity標籤中可以加入android:launchMode屬性,其值有四種:standardsingleTopsingleTasksingleInstance。不加屬性時默認就是standard,下面講講其它3個值的作用:

singleTopBack stack的棧底至棧頂依次爲Activity 1Activity 2時,即現在處於Activity 2界面,如果此時調用startActivity()函數再次啓動一個Activity 2時,在standard模式下,我們知道之後棧底至棧頂依次爲Activity 1Activity 2Activity2,而在singleTop模式下,則依次爲Activity 1Activity 2。從singleTop的字面意思看,它就是單個頂端的意思,即如果一個Activity已經處於棧頂了,就不會再新建同一個Activity,此時會依次調用Activity 2的生命週期函數onPause()->onNewIntent->onResume()。但如果不是處於棧頂,例如這裏啓動的是Activity 1,即使是singleTop模式,那棧中內容也會變爲Activity 1Activity 2Activity 1

singleTask從字面意思可看出,它代表被標誌爲singleTaskActivity只會在一個task即一個Back stack棧中。例如有兩個應用程序,每個程序啓動了兩個界面,如下圖所示:

                                                

                                     應用程序1                                         應用程序2

如上圖,假設此時應用程序2在後臺運行,並且其Activity Y位於它對應的Back stack的棧頂,而應用程序1正在運行並且當前顯示界面爲Activity 2,此時在Activity 2中調用startActivity()函數來啓動Activity Y,如果Activity Y處於standardsingleTop模式,那麼應用程序1的棧頂會加入一個新建的Activity Y,而應用程序2中的棧內容保持不變。而如果Activity Y處於singleTask,則應用程序1和應用程序2的棧都保持不變,直接跳轉至應用程序2的棧頂並顯示Activity YActivity Y會依次執行onNewIntent->onRestart->onStart->onResume生命週期函數。之後按下返回鍵就退到Activity X界面,繼續按返回鍵,應用程序2的棧就爲空了,這時它判斷到它剛剛是從應用程序1跳轉過來的,所有它不是回到Launcher主界面而是回到應用程序1的棧頂即Activity 2,然後繼續按返回鍵就退到Activity 1,再按返回鍵應用程序1的棧也爲空了,應用程序1知道它之前是直接從Launcher界面跳轉過來的,所有它就退出到Launcher界面了,即每個task會記住上一個跳轉到它的task是哪個,並在此task結束時跳到上一個task中。剛剛討論的是Activity Y處於singleTask模式時的情況,當Activity X處於singleTask模式,並且在Activity 2中啓動Activity X,根據singleTask的定義,這個時候不會新建Activity X的實例,而是直接跳轉到應用程序2的棧中的Activity X,但是Activity Y在棧頂,要顯示Activity X,就必須將Activity Y彈出棧並銷燬,所以這個情況要注意,只要覆蓋在Activity X上的Activity都會被Destroyed,如果是應用程序1Activity 2中啓動Activity 1,而且Activity 1singleTask模式,則應用程序1的棧中Activity 2會被銷燬,棧中只會保留最開始棧底的那個Activity 1。總結一下,singleTask模式的Activity,在整個系統中只會有一個實例,並且它被創建後,就始終位於某個固定的task中,至於處於哪個task,取決於第一次創建並啓動它的另外那個Activity啓動它時的intent參數有沒有設置標誌值FLAG_ACTIVITY_NEW_TASK,如果intent參數沒有設置標誌值,則它會安排在啓動它的那個Activitytask中,如果設置了標誌值,則會處於根據它的 taskAffinity值確定的那個task中。在它被創建後,以後再啓動它時都不會新創建實例了,而是直接跳轉到它所處的那個task中並顯示它。

singleInstancesingleTask模式類似,但它的限制條件更嚴格,被標誌爲singleInstanceActivity,當第一次被創建啓動時,系統一定給它創建一個單獨的task及對應的Back stack棧,之後它就永遠處於這個新創建的task中知道被銷燬,並且這個新的task中也保證只會有它這一個Activity,即使它啓動了別的Activity,被啓動的Activity也會在另外的task中,被它啓動的Activity會在那個ActivitytaskAffinity值確定的task中。singleInstanceActivity被創建啓動後,以後任何Activity再要啓動它時,都是直接跳轉到它所在的task中並顯示它,而不會創建它新的實例。例如在應用程序1中,首先啓動Activity 1,然後啓動Activity 2,而Activity 2被標誌爲singleInstance,這時的棧情況就如下:

                                            

                                 Task app1                                    某個單獨的task

如上圖,Activity 1會被安排在根據它的taskAffinity值即程序包名確認的task中,而Activity 2位於某個新創建的獨一無二的task中。這時如果接着在Activity 2中再啓動Activity 1,根據singleInstance的定義,新啓動的Activity 1是不會被安排在Activity 2task中的,而是安排在根據Activity 1taskAffinity值確認的那個task中,即之前已經存在的Task app1(這個task名字只是我這裏爲了講解而取的一個代號,系統中是用一個int值的ID來標記每個task),這時的棧情況如變爲如下圖所示:

                                       

                                   Task app1                                  某個單獨的task

可以發現Task app1中多了一個Activity 1,而Activity 2的棧保持不變。現在顯示的是Task app1中棧頂Activity 1,這時如果按返回鍵會出現什麼情況呢?要注意的是,返回鍵針對的是當前顯示的Activity所處的task,只有當前的task清空後,再按返回鍵纔會返回到之前的那個task,所以這時Task app1中只剩下一個Activity 1了,如果繼續按返回鍵,Task app1就被清空,由於它剛剛是由Activity 2的那個task跳轉過來的,所以接下來就返回到Activity 2。根據某個task清空後會返回到上個跳轉到它的task這個解釋,要注意一個情況,還是按上圖所示的狀態來解釋,即當前Task app1中有兩個Activity 1並且手機顯示的是其棧頂的Activity 1,如果之後不是按返回鍵而是按下Home鍵回到Launcher主界面,接着在Launcher中再次啓動應用程序1,這時Task app1恢復,並且顯示棧頂的Activity 1,接着按返回鍵,Task app1中只剩下一個Activity 1,再按返回鍵,Task app1被清空,但由於剛剛Task app1是從Launcher的主界面跳轉過來的,即它是從Launcher所處的那個task跳轉過來的,所以這時就退出到Launcher的界面了,而Activity 2一直在後臺,無法被Destroy,如果要將Activity 2退出,必須先通過某個方式跳轉到它的task並顯示它,然後按返回鍵將它退出,例如重新進入應用程序1,通過Activity 1上的按鈕或某個方式啓動到Activity 2,這時再按返回鍵,Activity 2纔可以退出了。

        上文中提到了啓動Activity時的參數intent的標誌值FLAG_ACTIVITY_NEW_TASK,另外還有其它兩個常用的標誌值FLAG_ACTIVITY_SINGLE_TOPFLAG_ACTIVITY_CLEAR_TOP。下面來講解這三個標誌值的作用。

FLAG_ACTIVITY_NEW_TASK這個標誌值在上文中已經有了比較詳細的介紹,它的作用是在啓動一個新Activity時確認這個新Activity將被安排在哪個task中,如果加了這個參數,被啓動的新的Activity就被安排在它的taskAffinity值所確定的task中,如果沒加這個參數,則會安排在啓動它的那個Activitytask中。這裏假設兩個Activity都沒有加特殊的啓動模式,即啓動模式都爲standard,如果加了其他啓動模式,那就要根據上文中講的各個啓動模式的規則來。很多資料甚至Android官網的資料中都說這個標誌值跟啓動模式裏的singleTask的作用相同,這個說法是不正確的,它們兩者完全是不同的概念,各有各的作用。在Launcher中啓動各應用程序時都加了這個標誌值。

FLAG_ACTIVITY_SINGLE_TOP這個標誌值與上文中講的啓動模式singleTop的作用一樣。

FLAG_ACTIVITY_CLEAR_TOP這個值的作用比較複雜,假設目前在Activity main界面,接着啓動某個Activity,暫且把這個被啓動的稱爲Activity flag,接着從Activity flag啓動到第三個界面Activity other,假設在沒有啓動模式等因素影響下,即它們在同一個task中,那麼棧的內容如下所示:

                          

這時接着從Activity other又啓動Activity flag,並且intent參數加入FLAG_ACTIVITY_CLEAR_TOP標誌值,那麼從位於棧中倒數第二個ActivityActivity flag開始(包括它),上面的所有Activity都被清除掉,即棧中現在只剩下了Activity main,然後會立即創建一個新的Activity flag並顯示,也就是說,如果啓動某個Activity時加了這個標誌值,系統會搜索這個被啓動的Activity將會被放置到的task,找到其中有沒有這個Activity的實例,如果有,就把那個task的棧中它上面所有的Activity都銷燬掉,如果這個Activity的啓動模式是standard,連它自身也會被銷燬掉並立即創建一個新的實例,如果啓動模式是singleTopsingleTask,那纔不會銷燬它自身,而是隻銷燬它上面的所有Activity然後直接顯示它。注意這裏重點標記的兩個字“將要”,是指根據啓動模式、這個被啓動的ActivitytaskAffinity值、啓動它時有沒有加標誌值FLAG_ACTIVITY_NEW_TASK這三個因素來確定,這個Activity將會安排到哪個task中,假設根據這三個因素它即將被安排在某個叫做dest(爲講解方便隨便取的一個名字)的task中,那麼系統就會到task dest的棧中尋找,有沒有這個Activity的實例存在,如果不存在它的實例,那麼這個標誌值沒什麼作用,跟不加的效果一樣;如果已有實例存在,那就把task dest的棧中它上面的所有Activity都銷燬,並且要根據它的啓動模式是否爲standard來確認是否連它自己也要銷燬。由於FLAG_ACTIVITY_NEW_TASK這個標誌值可能會影響將要被啓動的Activity會被放置在哪個task中,所以FLAG_ACTIVITY_CLEAR_TOP標誌值有時會與FLAG_ACTIVITY_NEW_TASK同時使用,以達到某個特殊的效果,在代碼中同時使用的它們方式爲:intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)

         AndroidManifest.xml文件中除了給Activityandroid:taskAffinity屬性之外,還有四個其它的跟taskAffinitytask相關的屬性值:allowTaskReparentingalwaysRetainTaskStateclearTaskOnLaunchfinishOnTaskLaunch

allowTaskReparenting從字面意思上看它是指允許這個被標記的Activity重新安排在別的task中。舉個例子,應用程序1中有Activity 1Activity 2兩個界面,應用程序2中有Activity A一個界面。目前應用程序2沒有啓動即還沒有創建task,並且Activity A在應用程序2中將屬性allowTaskReparenting置爲true;而應用程序1中啓動了Activity 1並接着啓動Activity A,然後又啓動Activity 2;,此時應用程序1的棧內容如下:

                        

                                 應用程序1task

這時如果按下Home鍵回到Launcher主界面,由於Activity AallowTaskReparenting值爲true,並且它是屬於應用程序2的,所以這時它就從應用程序1task中被移除到屬於它自己的task中,即應用程序2task,由於應用程序2目前還沒有創建task,它就需要先創建一個task,由於在Launcher主界面中啓動應用程序的標記爲android.intent.action.MAIN即主入口點的Activity時纔會創建task,所以系統會通過先創建一個虛擬的Activity A(只是做了標記,Activity A並沒有創建)來啓動應用程序2task,然後把應用程序1中移除的Activity A放到這個新task中,之後兩個應用程序的棧內容就如下:

                   

             應用程序1task                     應用程序2task

在應用程序2task中,紅字黑底的那個代表虛擬的目前還沒有創建的一個Activity A實例。之後如果從Launcher中啓動應用程序2,首先顯示的是從應用程序1task中移除過來的Activity A,然後按下返回鍵時,檢測到棧底還有一個虛擬的Activity A,所以會將其由虛擬變爲現實的,即創建一個Activity A的實例,這時棧中就剩下這個之前爲虛擬而現在爲真實存在的Activity了,繼續按返回鍵,應用程序2task結束。

alwaysRetainTaskState假設一個應用程序的task中有了一些Activity,當按Home鍵返回到Launcher主界面後,如果之後很長一段時間都不重新進入到這個應用程序,那麼系統很有可能會將task中除棧底的那個Activity之外的所有Activity丟棄掉。但如果棧底的那個ActivityalwaysRetainTaskState屬性值設爲true,則即使過了很長一段時間,系統通常也不會丟棄task中的任何Activity

clearTaskOnLaunch它與上一個屬性alwaysRetainTaskState作用相反,即不管經過多長時間,只要在棧底的ActivityclearTaskOnLaunch屬性爲true,那麼重新進入程序時所有除棧底外其他的Activity一定會被丟棄掉。這個屬性的優先級要高於alwaysRetainTaskState,即在棧底的Activity如果同時將這兩個屬性設爲true,那alwaysRetainTaskState的設置無效。

finishOnTaskLaunch與clearTaskOnLaunch類似,不過它只針對除棧底外的其它單個的Activity,如果某個Activity的finishOnTaskLaunch屬性值爲true,則重新進入程序時,這個Activity必定被丟棄掉了。它的優先級也高於alwaysRetainTaskState屬性。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章