1.2Activity的啓動模式與任務棧

Activity的啓動模式

系統通過“任務棧”對Activity進行管理,任務棧遵循“先進後出的原則”。Activity有四種啓動模式:standard,singleTop,singleTask,singleInstance。

1. standard :標準模式

  • 這是系統的默認模式,每次啓動一個Activity系統都會重新創建一個新的實例,不管這個實例是否已經存在。

  • 這是一種典型的多實例實現,一個任務棧中可以有多個實例,每個實例也可以運行在不同的任務棧中。

  • 在這種模式下,新啓動的Activity會添加到啓動它的Activity所在的任務棧中。注意,如果使用沒有任務棧的Context(比如ApplicationContext)啓動standard模式下的Activity,系統將會報錯,這個時候,就不能使用standard模式啓動了,可以在啓動時加一個FlagIntent.FLAG_ACTIVITY_NEW_TASK,這樣啓動的時候就會爲新的Activity創建一個新的任務棧。

Intent i = new Intent(getApplicationContext(), ActivityB.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);

2. singleTop:棧頂複用模式

  • 這種模式下,比如我們要啓動AcitivityB,如果已經有一個ActivityB位於任務棧中,並且它位於棧頂的位置,那麼新的ActivityB不會被重新創建,系統會調用當前ActivityB 的 onNewIntent(),而它的onCreate()和onStart()均不會被調用。

  • 如果任務棧中不存在AcitivityB,或者任務棧中的AcitvityB不是位於棧頂的位置,那麼系統會重新創建一個ActivityB。

3. singleTask:棧內複用模式

  • 這是一種單實例模式,在這種模式下,比如我們要啓動AcitivityD,只要目標任務棧中存在AcitivityD,那麼多次啓動此AcitivityD系統都不會創建新的實例,和singleTop一樣,系統會調用onNewIntent()。

  • 如果ActivityD以singleTask模式啓動,系統首先會尋找是否存在ActivityD所需的任務棧,如果不存在則會新建一個,然後把ActivityD添加進去。如果任務棧已經存在,就要看任務棧中是否存在ActivityD,如果不存在,則創建ActivityD並把它添加到任務棧的棧頂,否則,由於singleTask模式默認具有clearTop效果,位置處於ActivityD之上所有Activity都會自動出棧,讓ActivityD處於棧頂位置。

4. singleInstance:單實例模式

  • 這是一種加強版的singleTask模式,它除了具有singleTask所具有的所有特性外,還加強了一點,該模式下的Activity始終會單獨處於一個任務棧中。

  • 一個Activity以singleInstance模式第一次啓動時,系統會新建一個任務棧,並創建Activity的實例加入到這個任務棧中,後續再次啓動該Activity,系統都不會創建Activity實例,也不會爲它新建任務棧,除非這個任務棧被銷燬了。

如何給Activity指定啓動模式?

有兩種方法,第一種是通過AndroidManifest.xml給Activity指定launchMode:

<activity
    android:name="me.cv.main.SecondActivity"
    android:launchMode="singleTask"
    android:label="@string/app_name" />

另一種是通過在Intent中設置Intent Flag,比如:

Intent intent = new Intent()
intent.setClass(MainActivity.this, SecondActivity.class) ; 
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent) ;
  • 這兩種方式都可以爲Activity設置啓動模式,但是二者還是有區別的,首先,優先級上,第二種方式要優於第一種,當兩種方式同時存在,以第二種爲準;其次,兩種方式在限定範圍上有所不同,比如,第一種方式無法直接爲Activity設定Intent.FLAG_ACTIVITY_CLEAR_TOP 等標識,而第二種方式無法爲Activity指定singleInstance模式。

什麼是Activity所需的任務棧?

前面多次提到“任務棧”這個概念,那到底什麼是任務棧呢?Android任務棧簡單介紹如下:

  1. Android任務棧又稱爲Task,它是一個棧結構,具有後進先出的特性,用於存放我們的Activity組件。

  2. 我們每次打開一個新的Activity或者退出當前Activity都會在一個稱爲任務棧的結構中添加或者減少一個Activity組件,因此一個任務棧包含了一個activity的集合, android系統可以通過Task有序地管理每個activity,並決定哪個Activity與用戶進行交互:只有在任務棧棧頂的activity纔可以跟用戶進行交互。

  3. 在我們退出應用程序時,必須把所有的任務棧中所有的activity清除出棧時,任務棧纔會被銷燬。當然任務棧也可以移動到後臺, 並且保留了每一個activity的狀態. 可以有序的給用戶列出它們的任務, 同時也不會丟失Activity的狀態信息。

  4. 需要注意的是,一個App中可能不止一個任務棧,某些特殊情況下,單獨一個Activity可以獨享一個任務棧。還有一點就是一個Task中的Activity可以來自不同的App,同一個App的Activity也可能不在一個Task中。

任務棧關鍵屬性:taskAffinity

TaskAffinity特點如下:

  • “Affinity”這個單詞有“密切關係,吸引力”的意思, TaskAffinity 參數標識着Activity更傾向於加入的任務棧的名稱,默認情況下,一個應用中Activity都加入到和包名相同的任務棧中。

  • TaskAffinity 屬性一般跟singleTask模式、allowTaskReparenting屬性或Intent.FLAG_ACTIVITY_NEW_TASK結合使用,在其他情況下沒有實際意義。

  • TaskAffinity 屬性默認的值爲當前應用的包名,所以其值應和包名不一樣才能起作用。

  • TaskAffinity 屬性的值需包含至少一個“.”。

怎麼給Activity指定任務棧呢?

默認情況下,Activity所需任務棧的名字和應用的包名相同,如果想給Activity指定其他任務棧,可以在AndroidManifest.xml中給Activity添加taskAffinity屬性,而taskAffinity的用法有三種,分別是:

  • 1、taskAffinity與singleTask結合使用:
<activity    
    android:name="me.cv.main.SecondActivity"    
    android:taskAffinity="me.cv.test1"    
    android:launchMode="singleTask">
</activity>

我們已經知道,當Activity的launchMode被指定爲”singleTask”時,啓動該Activity會先尋找它所需的任務棧是否存在,我們這裏指定了taskAffinity,系統就會查詢是否存在名字叫做”me.cv.test1”的任務棧,不存在則創建並新建Activity實例放進去,否則繼續遵循singleTask的特性判斷是否任務棧是否存在該Acitivity,從而決定是新建Acitivity實例,還是調用onNewIntent並把位於該Acitivity之上的Activity出棧。

  • 2、taskAffinity與Intent.FLAG_ACTIVITY_NEW_TASK結合使用:
<activity    
    android:name="me.cv.main.SecondActivity"    
    android:taskAffinity="me.cv.test2">
</activity>
Intent intent = new Intent()
intent.setClass(MainActivity.this, SecondActivity.class) ; 
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent) ;

這種方法與跟singleTask結合使用類似,只有一點不同,就是它將遵循Intent.FLAG_ACTIVITY_NEW_TASK的規則,下文會詳細說明,這裏簡單講一下。這種情況下啓動Activity同樣會先尋找它所需的任務棧是否存在,在這裏是名字叫做”me.cv.test2”的任務棧,不存在則創建並新建Activity實例放進去。如果存在!注意了, 如果任務棧存在情況會比較複雜,如果該Activity已經存在與任務棧中,並且在棧底位置,那麼整個任務棧會切換到前臺,並保持棧中Activity的順序和狀態,就是說這個時候看到任務棧中棧頂位置那個Activity;如果任務棧存在但是Activity不在棧底位置,那麼仍然會新建Activity實例並放到棧頂位置。以上說的Activity都是指launchMode爲standar模式的,如果是其他模式,還會附帶其launchMode的效果。這也說明了一個問題,Intent.FLAG_ACTIVITY_NEW_TASK 並不能等價於singleTask模式,它並不具備 singleTask自帶的clearTop效果,Intent.FLAG_ACTIVITY_NEW_TASK 和啓動模式組合使用會產生不同的效果。

  • 3、taskAffinity與allowTaskReparenting結合使用:
<activity    
    android:name="me.cv.main.SecondActivity"    
    android:taskAffinity="me.cv.test3"
    android:allowTaskReparenting="true">
</activity>

這是個非常有意思用法,首先我們來聊聊allowTaskReparenting屬性,它的主要作用是Activity的遷移,即從一個task遷移到另一個task,這個遷移跟Activity的taskAffinity有關。當allowTaskReparenting的值爲“true”時,則表示Activity能從啓動的Task移動到有着affinity的Task(當這個Task進入到前臺時),當allowTaskReparenting的值爲“false”,表示它必須呆在啓動時呆在的那個Task裏。如果這個特性沒有被設定,元素(當然也可以作用在每次activity元素上)上的allowTaskReparenting屬性的值會應用到Activity上。默認值爲“false”。這樣說可能還比較難理解,我們舉個例子,比如現在有兩個應用A和B,A啓動了B的一個ActivityC,然後按Home鍵回到桌面,再單擊B應用時,如果此時,allowTaskReparenting的值爲“true”,那麼這個時候並不會啓動B的主Activity,而是直接顯示已被應用A啓動的ActivityC,我們也可以認爲ActivityC從A的任務棧轉移到了B的任務棧中。這就好比我們在路邊收養了一隻與主人走失了的貓,養着養着突然有一天,主人找上門來了,這隻貓也就被帶回去了。我們通過圖解來更好地理解這種情景:


圖片出自:http://blog.csdn.net/javazejian/article/details/52072131

常用的Intent Flag有哪些?

Intent.FLAG_ACTIVITY_NEW_TASK

設置此狀態,記住以下原則,首先會查找是否存在和被啓動的Activity具有相同的親和性的任務棧(即taskAffinity,注意同一個應用程序中的Activity的親和性在沒有修改的情況下是一樣的,一般來講就是包名,所以下面的a情況會在同一個棧中),如果有,則直接把這個棧整體移動到前臺,並保持棧中的狀態不變,即棧中的Activity順序不變,如果沒有,則新建一個棧來存放被啓動的Activity。(這裏說到的Activity都是standard模式。 )
  a. 前提 : Activity A和Activity B在同一個應用中。
  操作: Activity A啓動開僻Task堆棧(堆棧狀態:A),在Activity A中啓動Activity B, 啓動Activity B的Intent的Flag設爲FLAG_ACTIVITY_NEW_TASK,Activity B被壓入Activity A所在堆棧(堆棧狀態:AB)。
  原因: 默認情況下同一個應用中的所有Activity擁有相同的taskAffinity。
  
  b. 前提 : Activity A在名稱爲”TaskOne應用”的應用中, Activity C和Activity D在名稱爲”TaskTwo應用”的應用中。

  操作1 : 在Launcher中單擊“TaskOne應用”圖標,Activity A啓動開僻Task堆棧,命名爲TaskA(TaskA堆棧狀態: A),在Activity A中啓動Activity C, 啓動Activity C的Intent的Flag設爲FLAG_ACTIVITY_NEW_TASK,Android系統會爲Activity C開僻一個新的Task,命名爲TaskB(TaskB堆棧狀態: C), 長按Home鍵,選擇TaskA,Activity A回到前臺, 再次啓動Activity C(兩種情況:1.從桌面啓動;2.從Activity A啓動,兩種情況一樣), 這時TaskB回到前臺, Activity C顯示,供用戶使用, 即:包含FLAG_ACTIVITY_NEW_TASK的Intent啓動Activity的Task正在運行,則不會爲該Activity創建新的Task,而是將原有的Task返回到前臺顯示。

  操作2 : 在Launcher中單擊”TaskOne應用”圖標,Activity A啓動開僻Task堆棧,命名爲TaskA(TaskA堆棧狀態: A),在Activity A中啓動Activity C,啓動Activity C的Intent的Flag設爲FLAG_ACTIVITY_NEW_TASK,Android系統會爲Activity C開僻一個新的Task,命名爲TaskB(TaskB堆棧狀態: C), 在Activity C中啓動Activity D(TaskB的狀態: CD) 長按Home鍵, 選擇TaskA,Activity A回到前臺, 再次啓動Activity C(從桌面或者ActivityA啓動,也是一樣的),這時TaskB回到前臺, Activity D顯示,供用戶使用。說明了在此種情況下設置FLAG_ACTIVITY_NEW_TASK後,會先查找是不是有Activity C存在的棧,根據親和性(taskAffinity),如果有,剛直接把這個棧整體移動到前臺,並保持棧中的狀態不變,即棧中的順序不變。

Intent.FLAG_ACTIVITY_NO_ANIMATION
  禁止activity之間的切換動畫
  
Intent.FLAG_ACTIVITY_NO_HISTORY
  該Activity將不在stack中保留,用戶一離開它,這個Activity就關閉了。
  
Intent.FLAG_ACTIVITY_NO_USER_ACTION
  禁止activity調用onUserLeaveHint()函。onUserLeaveHint()作爲activity週期的一部分,它在activity因爲用戶要跳轉到別的activity而退到background時使用。比如,在用戶按下Home鍵(用戶的操作),它將被調用。比如有電話進來(不屬於用戶的操作),它就不會被調用。注意:通過調用finish()時該activity銷燬時不會調用該函數。
  
Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
  如果給Intent對象設置了這個標記,這個Intent對象被用於從一個存在的Activity中啓動一個新的Activity,那麼新的這個Activity不能用於接受發送給頂層activity的intent,這個新的activity的前一個activity被作爲頂部activity。
  
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
  如果在Intent中設置,並傳遞給Context.startActivity(),這個標誌將引發已經運行的Activity移動到歷史stack的頂端。 例如,假設一個Task由四個Activity組成:A,B,C,D。如果D調用startActivity()來啓動Activity B,那麼,B會移動到歷史stack的頂端,現在的次序變成A,C,D,B。如果FLAG_ACTIVITY_CLEAR_TOP標誌也設置的話,那麼這個標誌將被覆蓋。
  
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  這個標記在以下情況下會生效:1.啓動Activity時創建新的task來放置Activity實例;2.已存在的task被放置於前臺。系統會根據affinity對指定的task進行重置操作,task會壓入某些Activity實例或移除某些Activity實例。我們結合上面的FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET可以加深理解。
  
Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS
  api21加入。
  默認情況下通過FLAG_ACTIVITY_NEW_DOCUMENT啓動的activity在關閉之後,task中的記錄會相對應的刪除。如果爲了能夠重新啓動這個activity你想保留它,就可以使用者個flag,最近的記錄將會保留在接口中以便用戶去重新啓動。接受該flag的activity可以使用autoRemoveFromRecents去複寫這個request或者調用Activity.finishAndRemoveTask()方法。
  
Intent.FLAG_ACTIVITY_SINGLE_TOP
  singleTop一樣
  
Intent.FLAG_ACTIVITY_TASK_ON_HOME
  api11加入。
  把當前新啓動的任務置於Home任務之上,也就是按back鍵從這個任務返回的時候會回到home,即使這個不是他們最後看見的activity,注意這個標記必須和FLAG_ACTIVITY_NEW_TASK一起使用。
  
Intent.FLAG_DEBUG_LOG_RESOLUTION
  將log置爲可用狀態,如果設置了這個flag,那麼在處理這個intent的時候,將會打印相關創建日誌。
  
Intent.FLAG_EXCLUDE_STOPPED_PACKAGES和FLAG_INCLUDE_STOPPED_PACKAGES
  在3.1之後,系統的package manager增加了對處於“stopped state”應用的管理,這個stopped和Activity生命週期中的stop狀態是完全兩碼事,指的是安裝後從來沒有啓動過和被用戶手動強制停止的應用,與此同時系統增加了2個Flag:FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES ,來標識一個intent是否激活處於“stopped state”的應用。當2個Flag都不設置或者都進行設置的時候,採用的是FLAG_INCLUDE_STOPPED_PACKAGES的效果。
  
Intent.FLAG_FROM_BACKGROUND
  用來標識該intent的操作是一個後端的操作而不是一個直接的用戶交互。
FLAG_GRANT_PERSISTABLE_URI_PERMISSION
  api19添加
  當和FLAG_GRANT_READ_URI_PERMISSION 和/或FLAG_GRANT_WRITE_URI_PERMISSION一起使用時,uri權限在設置重啓之後依然存在直到用戶調用了revokeUriPermission(Uri, int)方法,這個標識僅爲可能的存在狀態提供許可,接受的應用必須要調用takePersistableUriPermission(Uri, int)方法去實際的變爲存在狀態。
  
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
  api21加入。
  當和FLAG_GRANT_READ_URI_PERMISSION 和/或FLAG_GRANT_WRITE_URI_PERMISSION一起使用時,uri的許可只用匹配前綴即可(默認爲全部匹配)。
FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION
  如果設置FLAG_GRANT_READ_URI_PERMISSION這個標記,Intent的接受者將會被賦予讀取Intent中URI數據的權限和lipData中的URIs的權限。當使用於Intent的ClipData時,所有的URIs和data的所有遞歸遍歷或者其他Intent的ClipData數據都會被授權。FLAG_GRANT_WRITE_URI_PERMISSION同FLAG_GRANT_READ_URI_PERMISSION只是相應的賦予的是寫權限。
  一個典型的例子就是郵件程序處理帶有附件的郵件。進入郵件需要使用permission來保護,因爲這些是敏感的用戶數據。然而,如果有一個指向圖片附件的URI需要傳遞給圖片瀏覽器,那個圖片瀏覽器是不會有訪問附件的權利的,因爲他不可能擁有所有的郵件的訪問權限。針對這個問題的解決方案就是per-URI permission:當啓動一個activity或者給一個activity返回結果的時候,呼叫方可以設置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION . 這會使接收該intent的activity獲取到進入該Intent指定的URI的權限,而不論它是否有權限進入該intent對應的content provider。
  
Intent.FLAG_RECEIVER_FOREGROUND
  api16添加。
  當發送廣播時,允許其接受者擁有前臺的優先級,更短的超時間隔。
  
Intent.FLAG_RECEIVER_NO_ABORT
  api19添加
  如果這是一個有序廣播,不允許接受者終止這個廣播,它仍然能夠傳遞給下面的接受者。
  
Intent.FLAG_RECEIVER_REGISTERED_ONLY
  如果設置了這個flag,當發送廣播的時,動態註冊的接受者纔會被調用,在Androidmanifest.xml 裏定義的Receiver 是接收不到這樣的Intent 的。
  
Intent.FLAG_RECEIVER_REPLACE_PENDING
  api8添加。
  如果設置了的話,ActivityManagerService就會在當前的系統中查看有沒有相同的intent還未被處理,如果有的話,就由當前這個新的intent來替換舊的intent,所以就會出現在發送一系列的這樣的Intent 之後,中間有些Intent 有可能在你還沒有來得及處理的時候, 就被替代掉了的情況

參考閱讀

《android開發藝術探索》
http://blog.csdn.net/self_study/article/details/48055011
http://blog.csdn.net/javazejian/article/details/52072131
http://blog.csdn.net/javazejian/article/details/52071885

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章