Activity任務棧與啓動模式

一、任務與任務棧的相關概念

  • 任務:任務是指一系列Activity的集合
  • 任務棧(返回棧):任務中的一系列Activity是以棧(一種後進先出的數據結構)的結構排列的,這個棧就被稱爲任務棧或者返回棧

通常情況下,我們從Launcher(桌面)點擊一個應用圖標啓動一個app,系統就會爲我們創建一個任務棧,這個任務棧的名字默認情況下就是app的包名,當然也可以自己指定,然後將那個被配置爲應用入口的Activity比如叫MainActivity實例放入棧中第一個位置。然後從MainActivity啓動一個新的Activity(SecondActivity),就會把SecondActivity的實例壓入棧中,放在棧中第二個位置也就是MainActivity的上面,這個時候MainActivity並沒有被回收,只是處於停止狀態。如果用戶按下返回鍵,SecondActivity會被從棧中彈出,並且MainActivity恢復爲運行狀態,也就是後進先出。用戶繼續按下返回鍵,MainActivity也會被從棧中彈出,並且因爲這個棧中沒有內容了,也將會被銷燬,界面上也會回到Home界面。過程示意圖如下:
默認情況下任務棧的工作流程
現在有這麼一種情況,分別有兩款應APP_M和APP_N,其中APP_M中有Activity_A,Activity_B,兩個Activity,應用APP_N中有Activity_C,Activity_D兩個Activity。首先從Launcher中啓動APP_M,Activity_A作爲APP_M入口被創建並放入一個棧中,這個棧就叫Stack_M吧。然後從Activity_A中啓動Activity_B,Activity_B會被放入Stack_M中,在Activity_A之上。按下Home鍵,回到Launcher界面,啓動APP_N,Activity_C作爲APP_N的入口被創建並放入一個新的棧中,這個棧就叫Stack_N吧。這個時候Stack_M將會被轉入後臺,不過其中的Activity都保持了原有的ui狀態,只不過處於停止狀態,Stack_N處於前臺狀態,再從Activity_C啓動Activity_D,Activity_D會被放入Stack_N中,在Activity_C之上。通過Home鍵回到Launcher界面,點擊APP_M的啓動圖標,Stack_M會重新回到前臺,Stack_N會進入後臺狀態,這個時候正在顯示的界面就是Activity_B。可以通過adb shell dumpsys activity activities查看相關任務棧的狀態。

我們改變一下上面的情況,不通過Launcher啓動APP_N,而是從Activity_B啓動Activity_C,再從Activity_C啓動Activity_D。通過adb命令我們可以得到如下的信息

TaskRecord{e677d21 #38 A=com.example.applicationm U=0 StackId=39 sz=4}                                                   
 Run #3: ActivityRecord{c15030d u0 com.example.applicationn/.ActivityD t38}                                              
 Run #2: ActivityRecord{5e24f6b u0 com.example.applicationn/.ActivityC t38}                                             
 Run #1: ActivityRecord{a06a0ac u0 com.example.applicationm/.ActivityB t38}                                             
 Run #0: ActivityRecord{76dd9aa u0 com.example.applicationm/.ActivityA t38}  

結果發現只有Stack_M被創建了,Activity_C和Activity_D都在Stack_M中,從這裏我們可以看出,在默認情況下,一個Activity所在的任務棧默認情況下就是啓動它的那個Activity所在的任務棧。

二、Activity的啓動模式

Activity有多種啓動模式,不同的啓動模式Activity在返回棧的表現就不一樣。啓動模式可以通過兩種模式設置:

  • manifest方式:在manifest文件中的標籤中有一個“launchMode”屬性,值是字符串類型。
  • Intent Flag方式:在通過intent啓動activity是,給intent設置相關的flag,也可以改變被啓動的activity的啓動模式,並且這種方式的優先級比通過manifest這種方式的優先級更高。

啓動模式總共有四種,分別如下:

  • standard:標準模式,這也是系統默認的模式。每次啓動一個Activity都會重新創建一個新的實例,哪怕它們屬於同一個Activity類,它所在的任務棧就是啓動它的那個Activity所在的棧。
    標準模式下任務棧的情況
  • singleTop:棧頂複用模式。再這種模式下,如果新Activity已經位於任務棧的棧頂,那麼此Activity不會創建新的實例,同時它的onNewIntent()方法會被回調。如果新的Activity實例已存在,但是不位於棧頂,那麼依然會重新創建新的Activity實例並放入棧頂。下面有個特殊案例: 現有一個任務棧M,裏面包含AB兩個Activity,A位於棧底,B位於棧頂,其中B的啓動模式位singleTop。另有一個任務棧N,裏面包含CD兩個Activity,C位於棧底,D位於棧頂。從D啓動B,儘管B的啓動模式是singleTop並且位於棧頂,但是這個B所在的棧不是N,在N棧中又沒有存在B,那麼就會創建新的B的實例,和standard模式一樣,新的B的實例所在的棧就是啓動它的那個Activity所在的棧。所以現在的情況就是,M棧中依然是AB兩個Activity,A位於棧底,B位於棧頂;N中有CDB三個Activity,C位於棧底,B位於棧頂。從這裏我們可以知道無論是singleTop還是standard,Activity所在的棧就是啓動它的那個Activity所在的棧。
  • singleTask:棧內複用模式。這是一種單實例模式,在這種模式下,只要Activity在一個棧中存在,那麼多次啓動此Activity都不會創建新的實例,只會回調onNewIntent()方法。下面有幾個例子:
  1. 目前任務棧S1中有ABC三個Activity,其中A位於棧底,C位於棧頂。這個時候Activity D以singleTask模式請求啓動,其所需要的任務棧是S2,由於S2和D的實例均不存在,所以系統會先創建任務棧S2,然後再創建D的實例並壓入棧S2中。
  2. 目前任務棧S1中有ADBC三個Activity,其中A位於棧底,C位於棧頂。這個時候Activity D以singleTask模式請求啓動,其所需要的任務棧是S1,由於任務棧存在且其中存在D的實例,系統會把D上面的Activity都彈出棧,並回調D的onNewIntent()方法。
  3. 目前存在棧S1中有AB兩個Activity,其中A位於棧底,B位於棧頂。存在棧S2中有CD兩個Activity,其中C位於棧底,D位於棧頂,並且D是singleTask模式。現在從B啓動D,由於D所需要的棧S2存在並且啓動存在D的實例並且位於棧頂,所以不會創建新的D的實例,只是S2任務棧將會變爲前臺狀態,S1會轉位後臺狀態。這時候連續按返回鍵,界面將會按照D->C->B->A->Home轉換,在C被銷燬後及S2被銷燬後,S1會轉爲前臺狀態。所以猜測任務棧本身也是以棧這種後進先出的數據數據結構存儲的,不過在Home界面有點特殊,這個時候按返回鍵不會執行彈出棧的操作。
  • singleInstance:單實例模式。這是一種加強的singleTask模式,它除了具有singleTask模式的所有特性以外,還加強了一點,那就是具有此種模式的Activity只能單獨地位於一個任務棧中。比如,Activity A 是singleTask模式,當A啓動後,系統會爲它創建一個新的任務棧,然後A獨自在這個新的任務棧中,由於棧內複用的特性,重複啓動A也不會創建新的實例。一個特殊例子:任務棧S1中有A一個Activity,然後從A啓動一個啓動模式爲singleInstance的Activity B,然後再從B啓動一個standard Activity C,A,C所需要的任務棧都爲S1,通過adb命令可以發現,Activity A,C位於同一個棧S1中,B位於一個單獨的任務棧中。所以得出結論,從一個啓動模式爲singleInstance的Activity A啓動一個新的activity B,B不會被壓入A所在的棧中,如果B所需要的棧存在,就創建B的實例並壓入那個棧中,如果不存在,則創建新的棧,並創建B的實例壓入新的棧中。

三、通過Intent Flag啓動Activity

通過startActivity(Intent intent)啓動一個Activity時,可以通過intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)這個方法指定不同的flag,其中有幾個flag可以影響Activity的啓動模式。

  • FLAG_ACTIVITY_NEW_TASK: 這個標記位的作用是爲Activity指定"singleTask"啓動模式,其效果與在manifest文件中指定singleTask模式相同。
  • FLAG_ACTIVITY_SINGLE_TOP: 這個標記位的作用是爲Activity指定"singleTop"啓動模式,其效果與在manifest文件中指定singleTop模式相同。
  • FLAG_ACTIVITY_CLEAR_TOP: 具有此標記位的Activity,當它啓動時,在同一個任務棧中所有位於它上面的Activity都要出棧。intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);通過這種方式聯用時,其效果和singleTask啓動模式一樣。如果被啓動的Activity採用的standard模式,那麼這個Activity本身和它之上的Activity都會出棧,並創新這個Activity新的實例壓入棧中。

四、其他與任務棧相關的屬性

  1. 任務棧歸屬的相關屬性
  • taskAffinity:前面經常提到一個Activity所需要的任務棧,那麼它所需要的任務棧是怎麼定義的呢,其實就是manifest文件中<activity>標籤的taskAffinity屬性,它指定這個activity所需任務棧的名字,系統通過它來標識對應的任務棧,所以爲了避免重複,如果需要自定義通常是由包名加上其他字符串組成。默認<activity>標籤的taskAffinity屬性繼承自application標籤,而application標籤的taskAffinity屬性默認值就是包名。
  • allowTaskReparenting:官方文檔上描述的意思大概是這樣的,當一個應用A啓動了應用B的某個Activity C後,那麼應用B啓動後,由於默認情況下應用B中所有Activity的taskAffinity都一樣,所以C所需要的任務棧已經啓動,如果這個Activity的allowTaskReparenting屬性爲true的話,C就會移動到B應用所在的任務棧中。不過經過測試好像沒有這種情況發生,懵逼!
  1. 清除任務棧的相關屬性
    如果一個用戶離開一個任務棧比較長的時間,系統會清除棧中根Activity(棧中最下面的那個Activity)以外的Activity,當用戶重新回到這個任務棧的時候,只有根Activity得以顯示。通過以下屬性可以改變這一默認行爲:
  • alwaysRetainTaskState:如果棧中的根Activity這個屬性被設置爲true,那麼上述行爲就不會發生,重回任務棧的時候,任務棧中的所有Activity都還存在。
  • clearTaskOnLaunch:如果棧中的根Activity這個屬性設置爲true,即使用戶離開這個棧很短一段時間,上述行爲也會發生,只有根Activity得以恢復。
  • finishOnTaskLaunch:這個屬性只對被設置了這個屬性的Activity有效,不會波及到整個任務棧,如果這個屬性設置爲true,跟clearTaskOnLaunch有點類似,即使用戶離開這個棧很短一段時間,finishOnTaskLaunch爲true的Activity就會被彈出棧。雖然官方文檔說這個屬性對根Activity也有效,然後經過測試發現僅對非根Activity有效。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章