launchMode,通俗點說,就是定義了Activity應該如何被launch的。那麼這幾種模式的區別以及應用場景,會有何不同呢?谷歌是基於什麼原因設計這幾種模式的呢?這幾種模式背後的工作原理是什麼呢?
任務和返回棧
在講解launchMode之前,先說說任務(Task)和返回棧(Back Stack,有些譯作回退棧、任務棧)這兩個概念。
A task is a collection of activities that users interact with when performing a certain job. The activities are arranged in a stack—the back stack—in the order in which each activity is opened.
任務是指當完成一個特定的工作時與使用者互動的一系列Activity。這些Activity按照開啟的順序存放在一個棧中,即返回棧。
通過定義可以知道,Activity會被按照開啟的順序存放。不難猜想,這種存放方式,是為了方便回退操作,也就不難解釋為什麼要用棧去存放。
當使用者點選啟動app的時候,這個app的返回棧就會跑到前臺,如果這個返回棧不存在的話,就會建立一個。當前Activity啟動另一個Activity的時候,新的Activity就會入棧,在棧頂。如果使用者點選返回按鈕,當前的Activity就會出棧並銷燬,之前的Activity就會被resume,如果棧為空,就會被銷燬掉。棧中的Activity永遠都不會被重新排序。
返回棧根據是否在前臺,可以分為在前臺顯示的返回棧,和置於後臺的返回棧。其中,置於後臺的返回棧中所有的Activity都處於stop狀態,使用者可以手動的去切換前後臺的返回棧狀態。
當系統記憶體不足時,系統會優先銷燬處於後臺的Activity。那麼問題來了。後臺銷燬Activity的優先順序是怎樣的呢?是將一個返回棧中的Activity都銷燬了過後,再去銷燬另一個,還是說,只是單純的按照Activity來銷燬回收呢?
任務管理
任務以及返回棧的管理,可以通過一系列的引數設定來進行,包括我們本文講解的launchMode,也是任務管理的一種方式。
taskAffinity
TaskAffinity即任務相關性,標識一個Activity所需要的返回棧的名字。預設情況下是包名。設定了相同taskAffinity屬性的Activity會被放進同一個棧中。一個返回棧的相關性(affinity)是由這個棧的根Activity的相關性(affinity)決定的。
taskAffinity屬性主要與singleTask或allowTaskReparenting結合使用,在其他情況下,這個屬性沒有作用。這是為什麼呢?
allowTaskReparenting
它的主要作用是Activity的遷移,從一個棧遷移到另一個棧,這個遷移跟Activity的taskAffinity有關。
clearTaskOnLaunch
這個屬性用來清除回退棧中除了根Activity的所有Activity,只對根Activity起作用。當設定為true時,每次重新進入app,只會看到根Activity。
finishOnTaskLaunch
這個屬性與clearTaskOnLaunch相反,它是將本Activity移除出去,而不影響其他的Activity。
alwaysRetainTaskState
這個屬性的作用是儲存返回棧的狀態,只對根Activity起作用。正常情況下,系統清理一個返回棧,會將根Activity之上的所有Activity都清除掉。設定該屬性後,系統會儲存當前的狀態。
啟動模式
啟動模式主要的作用是什麼呢?根據上面對任務及返回棧的介紹,它的作用是定義,一個新的Activity例項如何與當前的任務相關聯。它本身是任務的管理方式。
啟動模式有兩種定義方式,manifest裡定義和intent flag的方式。一種是類似配置式的,一種是程式碼層面的。可以大致推測,肯定是帶麼層面的優先順序高一些,但是程式碼方式劣處就是不啟動Activity就無法設定。Android中這種一般提供動態以及靜態方式的,套路都大致相同,一些區別各種優劣等。
其中manifest設定與intent flag中都包含對方沒有的方式。這也是兩者的一個區別。
launchMode
此處的launchMode專指Activity的launchMode屬性。其中有四種方式,這四種方式想必大家也都很清楚了,在這裡我不詳細展開了。
standard
最常見的一種模式,Activity的預設模式,每次啟動該模式的Activity,都會被重新建立,可以從屬不同的任務,也可以在一個任務中被建立多次。
它的應用場景特別廣泛, 一般不是特殊需求的話,都會去使用這種模式。
singleTop
如果在當前任務的棧頂,系統會呼叫Activity的onNewIntent()方法而不是重新建立一個新的例項。當使用者點選返回鍵時,當前Activity會被出棧,而不是會退到onNewIntent()之前的狀態。
它的應用場景也有一些。例如搜尋頁面,每次開啟,搜尋一些結果,點選詳情頁面,然後繼續搜尋。在比方說,通過通知開啟的頁面,如果該頁面存在,則更新,如果不存在,則建立。
singleTask
該模式只允許系統中存在一個該Activity的例項,如果當前例項不存在,則建立,如果已經存在,則將該例項之上的Activity全部出棧,走onNewIntent()。
singleTask適合作為程式入口點,當通過其他方式呼叫app時候,不會反覆建立主頁面。例如一般情況下的MainActivity,其他app呼叫的時候。
singleInstance
這種模式與singleTask十分類似,區別在於,持有該Activity的任務中只能包含一個Activity即它本身。
singleInstance適合需要與程式分離開的頁面,例如鬧鐘的響鈴介面,與鬧鐘的設定相分離。再例如系統的撥號介面。
Intent flags
此處討論的是通過程式碼方式進行設定,常見的有如下三種方式。
FLAG_ACTIVITY_NEW_TASK
使用一個新的返回棧來啟動Activity,跟上面討論的singleTask類似
FLAG_ACTIVITY_SINGLE_TOP
跟上面討論的singleTop類似
FLAG_ACTIVITY_CLEAR_TOP
這種方式是上面討論的launchMode中不存在的,它與singleTop的區別是,當已存在該例項了,會將它之上的Activity都出棧。
它經常與FLAG_ACTIVITY_NEW_TASK組合使用,可以達到singleTask的作用。
回到問題
幾種模式的區別以及應用場景,會有何不同呢?
答案見上面關於launchMode
谷歌是基於什麼原因設計這幾種模式的呢?
關於這個問題,我們先倒著來推理,即從使用場景去考慮,一般狀況下,我們開啟一個頁面,不在意是否是唯一,這個是最常見的需求,因此有了standard模式,這種也是預設的模式。當我們用搜尋頁面,當最頂層是搜尋頁面的時候,我不希望再開啟一個搜尋頁面,於是有了singleTop模式。當從其他App呼叫我們的app的時候,我只希望只顯示一個主頁面時,於是有了singleTask。關於singleInstance模式,則是希望與當前的頁面分離。
但是,我覺得谷歌並不能列舉出所有的場景,例如,我希望開啟一個頁面,記錄當前的路徑,例如a->b->c,這種場景下,四種模式裡面沒有包含。
如果從正面去推導的話,幾種啟動模式是任務及返回棧的管理。根據在棧中的狀態,大致可以分為如下幾類:
- 最常見的出棧入棧(standard)
- 當前棧中唯一(singleTask)
- 全域性唯一(singleInstance)
- 棧頂唯一(singleTop)
是不是很明晰了,有沒有其他的出現形式?肯定有的,例如棧底唯一,棧中唯一。但是這種方式可以等同於當前棧中唯一啊。
我們是否可以推匯出,谷歌是根據唯一性,來將啟動模式分為這幾種呢?intent flags則作為輔助的一些操作,例如部分出棧等等。當然這些也只是我的推測,不一定準確,哈哈。
這幾種模式背後的工作原理是什麼呢?
見任務及返回棧
記憶體回收的方式,是以Activity還是以任務作為基準回收?
目前已知的狀況時,如果返回棧置於後臺,當記憶體不足的時候,如果不設定alwaysRetainTaskState屬性的話,會將除了根Activity的所有Activity銷燬掉。可以確定是以返回棧為基準來進行回收。
taskAffinity屬性為什麼與singleTask一起使用才生效?
可以將Activity的launchMode根據是否在棧中唯一分為兩類
- standard、singleTop
- singleTask、singleInstance
第一類因為其唯一性,肯定是與taskAffinity不相容的。singleInstance建立的棧中只能包含本身,預設情況下都會單獨建立一個棧,指定與否都會單獨建立,因此設定沒有意義。而singleTask則是當前棧中唯一,適合作為根Activity,建立一個新的棧,這也是為什麼taskAffinity只能對根Activity起作用的緣故。
最後
我寫的內容不一定正確,一些問題的解釋也是根據我看到的資料來推到出來的,例如Activity為什麼會有四種啟動模式,如果大家有準確地答案,希望告知。另外,文中錯誤的地方,也希望指正。
最後,感謝大家的瀏覽,希望對您有所幫助。