總結一下啟動模式,以便日后回顧,整理自官方文檔
前言
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
啟動的activity
的task
棧中打開一個document
,和documentLaunchMode效果相等,有著不同的documents
的activity
的多個實例,將會出現在最近的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
的權限。當使用于Intent
的ClipData
時,所有的URIs
和data
的所有遞歸遍歷或者其他Intent
的ClipData
數據都會被授權。
同上,只是相應的賦予的是寫權限
當發送廣播時,允許其接受者擁有前臺的優先級,更短的超時間隔。
如果這是一個有序廣播,不允許接受者終止這個廣播,它仍然能夠傳遞給下面的接受者。
如果設置了這個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
移到另一個App
的task
中。
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開發相關的技術干貨,期待與你的交流,共勉。