深入理解Activity的啟動模式

總結一下啟動模式,以便日后回顧,整理自官方文檔

前言

Activity的啟動模式很重要,與回退棧以及重復實例息息相關,此文將就啟動模式介紹以下內容:

  • LaunchMode
  • Intent flag
  • TaskAffinity

前置知識

  • Activity采用棧式管理(任務與回退棧/Tasks and Back Stack)
  • 那么任務和回退棧分別是什么?
    • 任務是用戶在執行某項任務時與之交互的一系列活動,可以理解為活動集合。
    • 而活動按照被打開的順序放在堆棧中,我們稱之為回退棧
    • 回退棧采用 先進后出 的棧式結構
    • 每按下back時,棧頂Activity彈出。
    • 棧中的活動不會被重新排列,只有在活動啟動和銷毀時才從堆棧中推送和彈出。
示意圖

LaunchMode

LaunchMode的類別:

如看不下去= =,可直接跳至下方四種模式的異同

1. standard 標準模式

默認的啟動方式,特點是每次啟動時,都會創建活動的新實例,所有該活動的實例位于同一個task棧,遵循first in last out。

2. singleTop 棧頂復用

和standard很類似的啟動方式,也可以創建很多實例,特點是如果啟動目標Activity時,前臺已經有一個目標Activity的實例(即目標Activity處在task棧頂),則會重用該目標Activity實例,而非創建新實例。同時這個重用的實例,會接收到一個Activity.onNewIntent()的調用,以此獲取新的Intent
這個模式使用場景其實很少,通常只會避免相同程序的重復啟動,而不同程序間的跳轉情況與stardard完全一致,給定以下情景():

  • 消息推送:目前我們頁面停留在Activity A中,此時通知欄彈出Notification,點擊Notification再次啟動Activity A,那么為了避免Activity的重復打開,以及按下back時,回退到"重復頁面",則需要將該Activity設置為singleTop,并且重寫onNewIntent()來處理新請求。

3. singletask 棧內復用

該模式下的Activity在系統中只會有一個實例,如果啟動時,task棧中存在一個該活動的實例,則會復用該活動實例,并將task棧中該實例之上的activity全部出棧(銷毀過程中會調用Activity的生命周期回調方法)。同樣的,通過onNewIntent()方法接收新的Intent

4. singleInstance 單例模式

和singleTask類似,系統中只會存在一個活動實例。區別在于singleInstance模式下的Activity所處的task棧中僅存在該Activity一個實例,如果啟動其他任何Activity,那么都會在另一個task棧中啟動對應的Activity;而對于重復啟動自身時,則會復用原Activity,同樣通過onNewIntent()方法接收新的intent

四種模式的異同:

四種模式的異同

該圖摘自大佬Carson_Ho《Android基礎:最易懂的Activity啟動模式詳解》一文,若我表述不清,可移步原文

LaunchMode的設置方法

在Manifest的Activity配置中進行設置

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="yourPackage">

    <application
        ...>
        <activity android:name="..."
            android:launchMode="standard/singleTop/singleTask/singleInstance">

           
        </activity>
    </application>

</manifest>

至此關于LaunchMode的部分就介紹完了。


Intent Flag

通過Intent.addFlags()設置標志位是另一種設置啟動模式的方式,和LuanchMode方式有相交的部分。

注意:LaunchMode和Intent flags的優先級問題:
Intent設置方式的優先級 > Manifest設置方式

更多詳見Google官方文檔

首先我們先介紹以下最常見的幾個標記位屬性

等同于SingleTop

等同于SingleTask

清除位于其上層的所有Activity,與singleTask及其類似。區別在于:
當此標記不搭配FLAG_ACTIVITY_SINGLE_TOP標記時,會將已有的實例銷毀重建。
而搭配FLAG_ACTIVITY_SINGLE_TOP標記時則會復用已有的實例,并通過onNewIntent()方法得到新的intent

設置后,新Activity不會出現在recent apps里,即無法通過歷史列表回到該Activity。等同于在XML中指定Activity的屬性android:excludeFromRecents="true"

類似singleTask,區別在于當重復啟動目標活動時,不會將位于原活動之上的其他活動出棧,只是將原活動“置頂”。


以下部分好像沒那么"常用",至少很少看到有博客總結以下的標記位屬性。

啟動目標Activity時傳遞這個標記,則會導致所處的task棧被清空,然后在清空在之后的task棧中啟動目標Activity,也就是說,目標Activity成為這個空task棧的root Activity該標志必須配合FLAG_ACTIVITY_NEW_TASK一起使用。

在API 21中廢棄,現使用FLAG_ACTIVITY_NEW_DOCUMENT

官方文檔中是這么解釋的:如果設置,并且這個Intent用于從一個存在的Activity啟動一個新的Activity,那么,這個作為答復目標的Activity將會傳到這個新的Activity中。這種方式下,新的Activity可以調用setResult(int),并且這個結果值將發送給那個作為答復目標的Activity。
為了理解下面舉個例子:Activity A 通過startActivityForResult()啟動Activity B,之后Activity B 同樣通過startActivityForResult(),但附加FLAG_ACTIVITY_FORWARD_RESULT的flag來啟動Activity C。此時將會由C向AsetResult()

一般由系統調用,比如長按home鍵從歷史記錄中啟動。

此標志僅用于分屏多窗口模式。new Activity顯示在啟動它的活動(old Activity)的旁邊。只能與FLAG_ACTIVITY_NEW_TASK一起使用。另外,如果需要創建現有活動的新實例,則需要設置FLAG_ACTIVITY_MULTIPLE_TASK。

Android P Developer Priview中加入
設置后,如果設備上沒有能夠處理該intent的app,那么將會啟動一個instant app來進行處理。

這個標識用來創建一個新的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棧回到前臺的功能。

api 21之后加入的一個標識,用來在intent啟動的activitytask棧中打開一個document,和documentLaunchMode效果相等,有著不同的documentsactivity的多個實例,將會出現在最近的task列表中。單獨使用效果和documentLaunchMode="intoExisting"一樣,如果和FLAG_ACTIVITY_MULTIPLE_TASK一起使用效果就等同于documentLaunchMode="always"

禁用activity間的切換動畫

Activity不在回退棧中保留,一旦退出就銷毀,等同于設置noHistory屬性。
該方法會導致onActivityResult()失效,畢竟沒有返回的結果了嘛。

禁止activity調用onUserLeaveHint()onUserLeaveHint()作為activity周期的一部分,它在activity因為用戶要跳轉到別的activity而退到background時使用。比如,在用戶按下Home鍵(用戶的操作),它將被調用。比如有電話進來(不屬于用戶的操作),它就不會被調用。注意:通過調用finish()時該activity銷毀時不會調用該函數。

設置之后,再次重新啟動一個存在的Activity時,新的Activity會立即finish掉,原本的Activity則會作為棧頂Activity使用。

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

默認情況下通過FLAG_ACTIVITY_NEW_DOCUMENT啟動的activity在關閉之后,task中的記錄會相對應的刪除。如果為了能夠重新啟動這個activity你想保留它,就可以使用這個flag,最近的記錄將會保留在接口中以便用戶去重新啟動。接受該flag的activity可以使用autoRemoveFromRecents去復寫這個request或者調用Activity.finishAndRemoveTask()方法。

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

如果設置了這個flag,那么在處理這個intent的時候,將會打印相關創建日志。

用來標識該intent的操作是一個后端的操作而不是一個直接的用戶交互。

當和FLAG_GRANT_READ_URI_PERMISSION 和/或FLAG_GRANT_WRITE_URI_PERMISSION一起使用時,uri權限在設置重啟之后依然存在直到用戶調用了revokeUriPermission(Uri, int)方法,這個標識僅為可能的存在狀態提供許可,接受的應用必須要調用takePersistableUriPermission(Uri, int)方法去實際的變為存在狀態。

當和FLAG_GRANT_READ_URI_PERMISSION 和/或FLAG_GRANT_WRITE_URI_PERMISSION一起使用時,uri的許可只用匹配前綴即可(默認為全部匹配)。

如果設置FLAG_GRANT_READ_URI_PERMISSION這個標記,Intent的接受者將會被賦予讀取Intent中URI數據的權限和ClipData中的URIs的權限。當使用于IntentClipData時,所有的URIsdata的所有遞歸遍歷或者其他IntentClipData數據都會被授權。

同上,只是相應的賦予的是寫權限

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

如果這是一個有序廣播,不允許接受者終止這個廣播,它仍然能夠傳遞給下面的接受者。

如果設置了這個flag,當發送廣播的時,動態注冊的接受者才會被調用,在AndroidManifest.xml里定義的Receiver 是接收不到這樣的Intent的。

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

設置之后,廣播將對instant app中的廣播接收器可見。默認不可見。


taskAffinity

每個Activity都有一個taskAffinity屬性,用于指定活動具有"親和力"的task名稱,簡單來說就是指出該活動希望進入的task。該屬性默認為包名,除非Application或者Activity設置該屬性。
taskAffinity屬性必須要與singleTask啟動模式或者allowTaskReparenting屬性配對使用,否則沒有意義。

allowTaskReparenting屬性表明是否允許該Activity更換從屬task

  • taskAffinity + singleTask
    啟動Activity時,首先檢查是否存在與自己的taskAffinity相同的task,如果存在,那么將會把該Activity放入該task中;如果不存在,則新建taskAffinity指定的task。
  • taskAffinity + allowTaskReparenting
    用于實現把一個App里的Activity移到另一個Apptask中。

taskAffinity設置方法

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yourpackagename">

    <application
        ...>
        <activity 
            ...
            android:taskAffinity="com.example.yourtaskname"
            android:allowTaskReparenting="true/false"
            >
            ...
        </activity>
    </application>

</manifest>

引用


總結

  • 本文盡可能詳細的對Activity的啟動模式做出介紹,包括LaunchMode 四種模式的對比,Intent Flags各種標志的用法,以及對于taskAffinity的介紹。
  • 筆者水平有限,如有錯漏,歡迎指正。
  • 接下來我也會將所學的知識分享出來,有興趣可以繼續關注whd_Alive的Android開發筆記

歡迎關注whd_Alive的簡書

  • 不定期分享Android開發相關的技術干貨,期待與你的交流,共勉。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,202評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,742評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,297評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,688評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,875評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,438評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,183評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,384評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,612評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,093評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,330評論 2 377

推薦閱讀更多精彩內容