Android 深刻解析 Activity 的 launchMode 啓動模式,Intent Flag,taskAffinity

  最近看到一篇文章講launchMode,想到之前的一次面試,就問了這一個問題,最基本的你們都知道,可是詳細的我就迷糊了,最終失敗了,因此在此總結一下,但願可以幫助一下你們html

LaunchMode

  launchMode分爲四種:

  android

standard

  standard啓動模式爲最基本的啓動模式,默認爲該種啓動模式,特色就是每當發送一個intent請求打開該activity時,都會建立一個新的activity實例。實際使用狀況分爲兩種,一種是本應用打開,一種是跨應用打開:  面試


  • 本應用打開,新建立的activity實例放入本應用,即發送intent的task棧的頂部,這個比較簡單

  • 跨引用打開,這裏有一個須要注意的地方是跨應用打開的時候會在 Recent app 頁面顯示兩個獨立項,可是此時它們兩個 Activity 仍然是在一個棧中,很是感謝@夢想編制楠灬 的指正,跨應用打開在 Standard 模式下也是在一個棧中,雖然在 Recent app 頁面是兩個頁面:

    這裏寫圖片描述

    可是問題來了,又一次我偶然發現使用瀏覽器打開手機上的bilibili則是在同一個 Recent App 項中:

    這裏寫圖片描述

    使用命令adb shell dumpsys activity獲取手機的的activity棧的詳細信息

Running activities (most recent first):
      TaskRecord{9312e08 #811 A=com.htc.task.browser U=0 sz=2}
        Run #1: ActivityRecord{1409e01a u0 tv.danmaku.bili/.ui.video.VideoDetailsActivity t811}
        Run #0: ActivityRecord{64f3f7c u0 com.htc.sense.browser/.BrowserActivity t811}複製代碼

如上圖所示,com.htc.sense.browser/.BrowserActivity和tv.danmaku.bili/.ui.video.VideoDetailsActivity在同一個activity棧中,VideoDetailsActivity被打開在了BrowserActivity的task棧中,這是怎麼回事了,最後我發現有一個intent的flag變量有該做用:FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,只要將該flag設置進intent中就會將跨應用的activity打開在同一個 Recent app 中,感興趣的能夠試一下。shell

singleTop

  

  singleTop其實和standard幾乎同樣,和standard算一組,使用singleTop的Activity也能夠建立不少個實例。惟一不一樣的就是,若是調用的目標Activity已經位於調用者的Task的棧頂,則不建立新實例,而是使用當前的這個Activity實例,並調用這個實例的onNewIntent方法。

  這個使用場景比較少,可使用的例子好比用戶已經在當前activity,用戶點擊一條推送消息以後也須要跳轉到當前activity,那麼爲了不activity的重複打開,則須要將該activity設置爲singleTop而且複寫onNewIntent便可。

  若是是外部程序啓動singleTop的Activity,在Android 5.0以前新建立的Activity會位於調用者的Task中,5.0及之後會放入新的Task中,這點和standard同樣。後端

singleTask

  使用singleTask啓動模式的Activity 在系統中 只會存在一個實例。若是這個實例已經存在,intent就會經過onNewIntent傳遞到這個Activity,而且將棧中該activity之上的activity清除(銷燬過程會調用Activity生命週期回調),若是不存在新的Activity實例將被建立。

  這裏寫圖片描述api

 實際使用狀況也分爲兩組:瀏覽器


  • 本應用啓動,在一個應用中啓動設置爲singleTask的activity,若是該activity在task棧中不存在,則會建立一個新的實例放在棧頂,若是在activity的task棧中已經存在了該activity實例,則會將棧中該activity實例之上的其餘activity實例清空,而且會調用該activity的onNewIntent方法。最經常使用的使用例子就是首頁,好比首頁上面已經有了不少的activity,回到首頁就可使用這種方式,而後複寫首頁的onNewIntent方法。使用提示:onNewIntent方法中不能進行fragment的相關操做 http://blog.sina.com.cn/s/blog_5da93c8f0101rgb2.htmlapp


  • 跨應用啓動,因爲整個系統只能存在activity的一個實例,因此若是系統中不存在該activity,則會啓動一個新的task去啓動該activity,而且將該activity放入棧底。若是系統中存在該activity實例,則會直接啓動該activity,調用該activity的onNewIntent的方法,同時將該activity task棧上面的其餘activity清空(銷燬過程會調用Activity生命週期回調),此時若是用戶摁下返回鍵,那麼將在singleTask activity的task棧中操做,即返回該棧中singleTask activity的上一個activity直到該棧中無activity時纔會返回到最開始啓動singleTask activity的activity中。還有一種狀況是singleTask Activity所在的應用進程存在,可是singleTask Activity實例不存在,那麼從別的應用啓動這個Activity,新的Activity實例會被建立,並放入到所屬進程所在的Task中,並位於棧頂位置。ide


注意若是使用了singleTask,FLAG_ACTIVITY_RESET_TASK_IF_NEEDED這個flag將會失效。

  須要特別注意的是:

  1.在4.x和以前的系統下,A1(startActivityForResult)->A2(singleTask, startActivityForResult)->A3->A4,當A1打開A2以後會當即回調onActivityResult()函數,A2打開A3仍然能夠正常回調onActivityResult();可是從5.0開始,A1打開A2的時候 onActivityResult() 函數也能正常的回調,不會當即回調。

  2.在4.x和以前的系統下,A1(startActivityForResult)->A2(singleTask, startActivityForResult)->A3,生命週期的流程是函數

A1.onStart()->A1.onResume()(startActivityForResult打開A2)->A1.onPause() ->A1.onActvitiyResult->A1.onResume()->A1.onPause()->A2.onStart() ->A2.onResume()->A1.onStop()(startActivityForResult打開A3)->A2.onPause() ->A3.onStart()->A3.onResume()->A2.onStop()複製代碼

(注:onCreate和onDestroy這兩個生命週期沒有變化,因此沒有加進去,還有一個是A1.onStop生命週期在A2.onResume以後這個是不必定的,視狀況而定),上面的變化必需要是startActivityForResult()+5.0以前的系統纔會出現,能夠推測是因爲onActivityResult()函數引發的這個問題。

singleInstance

  和singleTask相似,在系統中 只會存在一個實例,惟一的區別就是系統不會在singleInstance activity的task棧中啓動任何其餘的activity,singleInstance activity棧中僅僅只能有該activity的實例,其餘任何從這個activity啓動的activity都會在其餘的棧中被打開。

  雖然使用adb shell dumpsys activity能夠看到singleInstance activity在一個獨立的task中,可是在任務管理器中,仍是顯示的一個

  這裏寫圖片描述

  須要特別注意的是:

  1.A1->A2(SingleInstance),摁下 Home 鍵以後,點擊應用圖標再次進入應用,返回的是 A1 頁面,這是由於 A2 在另外一個單獨的 Activity task 棧中,點擊圖標返回的是主 Activity 棧,因此此時顯示的 A1 頁面,而不是 A2 頁面。

  2.注意singleInstance的返回鍵的處理和上面3個 mode 有區別,若是是使用正常的 startActivity 進行的啓動,啓動順序A1->A2->A3(singleInstance)->A4,在A4頁面摁下back鍵,返回的是A2,再返回A1,接着再次摁下back鍵,返回的纔是A3;若是是使用 startActivityForResult 啓動的,在4.x和以前的系統下,表現和以前是同樣的,可是在5.0開始,A1->A2-A3(singleInstance + startActivityForResult)->A4,在A4摁下返回鍵,返回的是A3->A2->A1,這個須要着重說明一下。

  3.另外一個比較重要的是,在4.x和以前的系統下,A1(startActivityForResult)->A2(singleInstance, startActivityForResult)->A3->A4,當A1打開A2以後會當即回調onActivityResult()函數,A2打開A3也會當即回調onActivityResult()函數;可是從5.0開始,A1打開A2和A2打開A3的兩種狀況下 onActivityResult() 函數都能正常的回調,不會當即回調。

  4.還有一個比較重要的是,在4.x和以前的系統下,A1(startActivityForResult)->A2(singleInstance, startActivityForResult)->A3,生命週期的流程是

A1.onStart()->A1.onResume()(startActivityForResult打開A2)->A1.onPause() ->A1.onActvitiyResult->A1.onResume()->A1.onPause()->A2.onStart() ->A2.onResume()->A1.onStop()(startActivityForResult打開A3)->A2.onPause() ->A2.onActivityResult()->A2.onResume()->A2.onPause()->A3.onStart() ->A3.onResume()->A2.onStop()複製代碼

和singleTask同樣,也是必需要是startActivityForResult()+5.0以前的系統纔會出現,區別就只是A2打開A3的狀況不一樣。

Intent Flag

  Intent的flag有不少,介紹一下吧

FLAG_ACTIVITY_BROUGHT_TO_FRONT

  比方說我如今有A,在A中啓動B,在A中Intent中加上這個標記。此時B就是以FLAG_ACTIVITY_BROUGHT_TO_FRONT 這個啓動的,在B中再啓動C,D(正常啓動C,D),若是這個時候在D中再啓動B,這個時候最後的棧的狀況是 A,C,D,B.。

FLAG_ACTIVITY_CLEAR_TASK

  若是在調用startActivity時傳遞這個標記,該task棧中的其餘activity會先被清空,而後該activity在該task中啓動,也就是說,這個新啓動的activity變爲了這個空task的根activity。全部老的activity都結束掉。該標誌必須和FLAG_ACTIVITY_NEW_TASK一塊兒使用。

FLAG_ACTIVITY_CLEAR_TOP

  若是該activity已經在task中存在,而且設置了該task,系統不會啓動新的 Activity 實例,會將task棧裏該Activity之上的全部Activity一概結束掉,而後將Intent發給這個已存在的Activity。Activity收到 Intent以後,或者在onNewIntent()裏作下一步的處理,或者自行結束而後從新建立本身。若是 Activity 在 AndroidMainifest.xml 裏將啓動模式設置成默認standard模式,而且 Intent 裏也沒有設置 FLAG_ACTIVITY_SINGLE_TOP,那麼他將會結束而且重啓;不然則會傳遞到onNewIntent方法,FLAG_ACTIVITY_CLEAR_TOP 還能夠和 FLAG_ACTIVITY_NEW_TASK 配合使用,用來啓動一個task棧的根activity,他將會把該棧清空爲根狀態,好比從notification manager啓動activity。

FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET

  已經廢棄,請使用FLAG_ACTIVITY_NEW_DOCUMENT

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

  設置完以後,新的activity將不會添加到當前activity列表中,當某些狀況下咱們不但願用戶經過歷史列表回到咱們的Activity的時候這個標記比較有用。他等同於在XML中指定Activity的屬性android:excludeFromRecents=」true」。

FLAG_ACTIVITY_FORWARD_RESULT

  若是設置,而且這個Intent用於從一個存在的Activity啓動一個新的Activity,那麼,這個做爲答覆目標的Activity將會傳到這個新的Activity中。這種方式下,新的Activity能夠調用setResult(int),而且這個結果值將發送給那個做爲答覆目標的Activity。

FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY

  通常由系統調用,好比長摁home鍵從歷史記錄中啓動。

FLAG_ACTIVITY_MULTIPLE_TASK

  這個標識用來建立一個新的task棧,而且在裏面啓動新的activity(全部狀況,無論系統中存在不存在該activity實例),常常和FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK一塊兒使用。這上面兩種使用場景下,若是沒有帶上FLAG_ACTIVITY_MULTIPLE_TASK標識,他們都會使系統搜索存在的task棧,去尋找匹配intent的一個activity,若是沒有找到就會去新建一個task棧;可是當和FLAG_ACTIVITY_MULTIPLE_TASK一塊兒使用的時候,這兩種場景都會跳過搜索這步操做無條件的建立一個新的task。和FLAG_ACTIVITY_NEW_TASK一塊兒使用須要注意,儘可能不要使用該組合除非你完成了本身的頂部應用啓動器,他們的組合使用會禁用已經存在的task棧回到前臺的功能。

FLAG_ACTIVITY_NEW_DOCUMENT

  api 21以後加入的一個標識,用來在intent啓動的activity的task棧中打開一個document,和documentLaunchMode效果相等,有着不一樣的documents的activity的多個實例,將會出如今最近的task列表中。單獨使用效果和documentLaunchMode=」intoExisting」同樣,若是和FLAG_ACTIVITY_MULTIPLE_TASK一塊兒使用效果就等同於documentLaunchMode=」always」。

FLAG_ACTIVITY_NEW_TASK

  設置此狀態,記住如下原則,首先會查找是否存在和被啓動的Activity具備相同的親和性的任務棧(即taskAffinity,注意同一個應用程序中的activity的親和性在沒有修改的狀況下是同樣的,因此下面的a狀況會在同一個棧中),若是有,剛直接把這個棧總體移動到前臺,並保持棧中的狀態不變,即棧中的activity順序不變,若是沒有,則新建一個棧來存放被啓動的activity。

  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),若是有,剛直接把這個棧總體移動到前臺,並保持棧中的狀態不變,即棧中的順序不變。

FLAG_ACTIVITY_NO_ANIMATION

  禁止activity之間的切換動畫

FLAG_ACTIVITY_NO_HISTORY

  該Activity將不在stack中保留,用戶一離開它,這個Activity就關閉了。

FLAG_ACTIVITY_NO_USER_ACTION

  禁止activity調用onUserLeaveHint()函。onUserLeaveHint()做爲activity週期的一部分,它在activity由於用戶要跳轉到別的activity而退到background時使用。好比,在用戶按下Home鍵(用戶的操做),它將被調用。好比有電話進來(不屬於用戶的操做),它就不會被調用。注意:經過調用finish()時該activity銷燬時不會調用該函數。

FLAG_ACTIVITY_PREVIOUS_IS_TOP

  若是給Intent對象設置了這個標記,這個Intent對象被用於從一個存在的Activity中啓動一個新的Activity,那麼新的這個Activity不能用於接受發送給頂層activity的intent,這個新的activity的前一個activity被做爲頂部activity。

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標誌也設置的話,那麼這個標誌將被覆蓋。

FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

  這個標記在如下狀況下會生效:1.啓動Activity時建立新的task來放置Activity實例;2.已存在的task被放置於前臺。系統會根據affinity對指定的task進行重置操做,task會壓入某些Activity實例或移除某些Activity實例。咱們結合上面的FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET能夠加深理解。

FLAG_ACTIVITY_RETAIN_IN_RECENTS

  api21加入。

  默認狀況下經過FLAG_ACTIVITY_NEW_DOCUMENT啓動的activity在關閉以後,task中的記錄會相對應的刪除。若是爲了可以從新啓動這個activity你想保留它,就可使用者個flag,最近的記錄將會保留在接口中以便用戶去從新啓動。接受該flag的activity可使用autoRemoveFromRecents去複寫這個request或者調用Activity.finishAndRemoveTask()方法。

FLAG_ACTIVITY_SINGLE_TOP

  singleTop同樣

FLAG_ACTIVITY_TASK_ON_HOME

  api11加入。

  把當前新啓動的任務置於Home任務之上,也就是按back鍵從這個任務返回的時候會回到home,即便這個不是他們最後看見的activity,注意這個標記必須和FLAG_ACTIVITY_NEW_TASK一塊兒使用。

FLAG_DEBUG_LOG_RESOLUTION

  將log置爲可用狀態,若是設置了這個flag,那麼在處理這個intent的時候,將會打印相關建立日誌。

FLAG_EXCLUDE_STOPPED_PACKAGESFLAG_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的效果。

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)方法去實際的變爲存在狀態。

FLAG_GRANT_PREFIX_URI_PERMISSION

  api21加入。

  當和FLAG_GRANT_READ_URI_PERMISSION 和/或FLAG_GRANT_WRITE_URI_PERMISSION一塊兒使用時,uri的許可只用匹配前綴便可(默認爲所有匹配)。

FLAG_GRANT_READ_URI_PERMISSIONFLAG_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。

FLAG_RECEIVER_FOREGROUND

  api16添加。

  當發送廣播時,容許其接受者擁有前臺的優先級,更短的超時間隔。

FLAG_RECEIVER_NO_ABORT

  api19添加

  若是這是一個有序廣播,不容許接受者終止這個廣播,它仍然可以傳遞給下面的接受者。

FLAG_RECEIVER_REGISTERED_ONLY

  若是設置了這個flag,當發送廣播的時,動態註冊的接受者纔會被調用,在Androidmanifest.xml 裏定義的Receiver 是接收不到這樣的Intent 的。

FLAG_RECEIVER_REPLACE_PENDING

  api8添加。

  若是設置了的話,ActivityManagerService就會在當前的系統中查看有沒有相同的intent還未被處理,若是有的話,就由當前這個新的intent來替換舊的intent,因此就會出如今發送一系列的這樣的Intent 以後,中間有些Intent 有可能在你尚未來得及處理的時候, 就被替代掉了的狀況

taskAffinity

  每一個Activity都有taskAffinity屬性,這個屬性指出了它但願進入的Task。若是一個Activity沒有顯式的指明該 Activity的taskAffinity,那麼它的這個屬性就等於Application指明的taskAffinity,若是 Application也沒有指明,那麼該taskAffinity的值就等於包名。而Task也有本身的affinity屬性,它的值等於它的根 Activity的taskAffinity的值。

  TaskAffinity屬性主要和SingleTask啓動模式或者allowTaskReparenting屬性配對使用,在其餘狀況下沒有意義。當TaskAffinity和singleTask啓動模式配對使用的時候,他是具備該模式的Activity的目前任務棧的名字,待啓動的Activity會運行在名字和TaskAffinity相同的任務棧中。allowTaskReparenting用於配置是否容許該activity能夠更換從屬task,一般狀況兩者連在一塊兒使用,用於實現把一個應用程序的Activity移到另外一個應用程序的Task中。allowTaskReparenting用來標記Activity可否從啓動的Task移動到taskAffinity指定的Task,默認是繼承至application中的allowTaskReparenting=false,若是爲true,則表示能夠更換;false表示不能夠。

  若是加載某個Activity的intent,Flag被設置成FLAG_ACTIVITY_NEW_TASK時,它會首先檢查是否存在與本身taskAffinity相同的Task,若是存在,那麼它會直接宿主到該Task中,若是不存在則從新建立Task。

引用


droidyue.com/blog/2015/0…

blog.csdn.net/lygglobetec…

相關文章
相關標籤/搜索