我打賭你一定沒搞明白的Activity啟動模式

一個應用程序當中通常都會包含很多個Activity,每個Activity都是一個具有特定的功能,并且可以讓用戶進行操作的組件。另外,Activity之間可以相互啟動,當前應用的Activity甚至可以去啟動其他應用的Activity。比如你的應用希望去發送一封郵件,你就可以定義一個具有"send"動作的Intent,并且傳入一些數據,如對方郵箱地址、郵件內容等。這樣,如果另外一個應用程序中的某個Activity聲明自己是可以響應這種Intent的,那么這個Activity就會被打開。當郵件發送之后,按下返回鍵仍然還是會回到你的應用程序當中,這讓用戶看起來好像剛才那個編寫郵件的Activity就是你的應用程序當中的一部分。所以說,即使有很多個Activity分別都是來自于不同應用程序的,Android系統仍然可以將它們無縫地結合到一起。那這一切是怎么實現的呢?這就要講到本文要介紹的Activity任務棧以及Activity啟動模式了。

任務棧是什么

任務棧Task,是一種用來放置Activity實例的容器,他是以棧的形式進行盛放,也就是所謂的先進后出,主要有2個基本操作:壓棧和出棧,其所存放的Activity是不支持重新排序的,只能根據壓棧和出棧操作更改Activity的順序。

啟動一個Application的時候,系統會為它默認創建一個對應的Task,用來放置根Activity。默認啟動Activity會放在同一個Task中,新啟動的Activity會被壓入啟動它的那個Activity的棧中,并且顯示它。當用戶按下回退鍵時,這個Activity就會被彈出棧,按下Home鍵回到桌面,再啟動另一個應用,這時候之前那個Task就被移到后臺,成為后臺任務棧,而剛啟動的那個Task就被調到前臺,成為前臺任務棧,Android系統顯示的就是前臺任務棧中的Top實例Activity。

任務棧的作用

以往基于應用(application)的程序開發中,程序具有明確的邊界,一個程序就是一個應用,一個應用為了實現功能可以采用開辟新線程甚至新進程來輔助,但是應用與應用之間不能復用資源和功能。而Android引入了基于組件開發的軟件架構,雖然我們開發android程序,仍然使用一個apk工程一個Application的開發形式,但是對于Aplication的開發就用到了Activity、service等四大組件,其中的每一個組件,都是可以被跨應用復用的,這就是android的神奇之處。雖然組件可以跨應用被調用,但是一個組件所在的進程必須是在組件所在的Aplication進程中。由于android強化了組件概念,弱化了Aplication的概念,所以在android程序開發中,A應用的A組件想要使用拍照或錄像的功能就可以不用去針對Camera類進行開發,直接調用系統自帶的攝像頭應用(稱其B應用)中的組件(稱其B組件)就可以了,但是這就引發了一個新問題,A組件運行在A應用中,B組件運行在B應用中,自然都不在同一個進程中,那么從B組件中返回的時候,如何實現正確返回到A組件呢?Task就是來負責實現這個功能的,它是從用戶角度來理解應用而建立的一個抽象概念。因為用戶所能看到的組件就是Activity,所以Task可以理解為實現一個功能而負責管理所有用到的Activity實例的棧。

棧是一個先進后出的線性表,根據Activity在當前棧結構中的位置,來決定該Activity的狀態。正常情況下,當一個Activity啟動了另一個Activity的時候,新啟動的Activity就會置于任務棧的頂端,并處于活動狀態,而啟動它的Activity雖然成功身退,但依然保留在任務棧中,處于停止狀態,當用戶按下返回鍵或者調用finish()方法時,系統會移除頂部Activity,讓后面的Activity恢復活動狀態。當然,世界不可能一直這么“和諧”,可以給Activity設置一些“特權”,來打破這種“和諧”的模式,這種特權,就是通過在AndroidManifest文件中的屬性andorid:launchMode來設置或者通過Intent的flag來設置的,下面就先介紹下Activity的幾種啟動模式。

standard

默認模式,可以不用寫配置。在這個模式下,都會默認創建一個新的實例。因此,在這種模式下,可以有多個相同的實例,也允許多個相同Activity疊加。應用場景:絕大多數Activity。

standard.png

如果以這種方式啟動的Activity被跨進程調用,在5.0之前新啟動的Activity實例會放入發送Intent的Task的棧的頂部,盡管它們屬于不同的程序,這似乎有點費解看起來也不是那么合理,所以在5.0之后,上述情景會創建一個新的Task,新啟動的Activity就會放入剛創建的Task中,這樣就合理的多了。

singleTop

棧頂復用模式,如果要開啟的activity在任務棧的頂部已經存在,就不會創建新的實例,而是調用 onNewIntent() 方法。避免棧頂的activity被重復的創建。應用場景:在通知欄點擊收到的通知,然后需要啟動一個Activity,這個Activity就可以用singleTop,否則每次點擊都會新建一個Activity。當然實際的開發過程中,測試妹紙沒準給你提過這樣的bug:某個場景下連續快速點擊,啟動了兩個Activity。如果這個時候待啟動的Activity使用 singleTop模式也是可以避免這個Bug的。

2(1).png

同standard模式,如果是外部程序啟動singleTop的Activity,在Android 5.0之前新創建的Activity會位于調用者的Task中,5.0及以后會放入新的Task中。

singleTask

棧內復用模式, activity只會在任務棧里面存在一個實例。如果要激活的activity,在任務棧里面已經存在,就不會創建新的activity,而是復用這個已經存在的activity,調用 onNewIntent() 方法,并且清空這個activity任務棧上面所有的activity。應用場景:大多數App的主頁。對于大部分應用,當我們在主界面點擊回退按鈕的時候都是退出應用,那么當我們第一次進入主界面之后,主界面位于棧底,以后不管我們打開了多少個Activity,只要我們再次回到主界面,都應該使用將主界面Activity上所有的Activity移除的方式來讓主界面Activity處于棧頂,而不是往棧頂新加一個主界面Activity的實例,通過這種方式能夠保證退出應用時所有的Activity都能報銷毀。
在跨應用Intent傳遞時,如果系統中不存在singleTask Activity的實例,那么將創建一個新的Task,然后創建SingleTask Activity的實例,將其放入新的Task中。

1:假如目前有個任務棧T1中的情況是ABC,這個時候ActivityD以singleTask模式請求啟動,其所需要的任務棧正是T1,則系統會直接創建D的實例并將其入棧到T1中。

singleTask1.png

2:假如DActivity啟動所需要的任務棧為T2,由于T2和D的實例均不存在,那么系統會先創建任務棧T2,然后再創建D的實例并將其入棧到T2中。我們可以通過設置Activity的taskAffinity屬性來模擬這一場景。

<activity 
android:name=".SingleTaskActivity" 
android:label="singleTask launchMode" 
android:launchMode="singleTask" 
android:taskAffinity="">
</activity>
singleTask2.png

3:如果D所需的任務棧為T3,并且當前任務棧T3的情況為ADBC,根據棧內復用的原則,此時D不會重新創建,系統會把D切換到棧頂并調用其onNewIntent()方法,同時由于singleTask默認具有ClearTop的效果,會導致棧內所有在D上面的Activity全部出棧,于是最終T3的情況為AD。

singleTask3.png

4:假如目前有兩個任務棧,前臺任務棧T4的情況為AB,后臺任務棧t4里存有CD,假設CD的啟動模式均為singleTask,現在由B去啟動D,那么整個后臺任務都會被切換到前臺,這個時候整個棧就變成了ABCD。

singleTask4.png

5:假如上面的其他條件不變,B啟動的是C而不是D,那么整個棧的情況就變成了ABC,因為D在C上面,會被清理出棧。

singleTask5.png

singleInstance

單一實例模式,整個手機操作系統里面只有一個實例存在。不同的應用去打開這個activity 共享公用的同一個activity。他會運行在自己單獨,獨立的任務棧里面,并且任務棧里面只有他一個實例存在。應用場景:呼叫來電界面。這種模式的使用情況比較罕見,在Launcher中可能使用。或者你確定你需要使Activity只有一個實例。建議謹慎使用。

范冰冰.pic.jpg

設置Intent的Flag

系統提供了兩種方式來設置一個Activity的啟動模式,除了在AndroidManifest文件中設置以外,還可以通過Intent的Flag來設置一個Activity的啟動模式,下面我們在簡單介紹下一些Flag。

FLAG_ACTIVITY_NEW_TASK

使用一個新的Task來啟動一個Activity,但啟動的每個Activity都講在一個新的Task中。該Flag通常使用在從Service中啟動Activity的場景,由于Service中并不存在Activity棧,所以使用該Flag來創建一個新的Activity棧,并創建新的Activity實例。

FLAG_ACTIVITY_SINGLE_TOP

使用singletop模式啟動一個Activity,與指定android:launchMode=“singleTop”效果相同。

FLAG_ACTIVITY_CLEAR_TOP

使用SingleTask模式來啟動一個Activity,與指定android:launchMode=“singleTask”效果相同。

FLAG_ACTIVITY_NO_HISTORY

Activity使用這種模式啟動Activity,當該Activity啟動其他Activity后,該Activity就消失了,不會保留在Activity棧中。

LaunchMode與StartActivityForResult

我們在開發過程中經常會用到StartActivityForResult方法啟動一個Activity,然后在onActivityResult()方法中可以接收到上個頁面的回傳值,但你有可能遇到過拿不到返回值的情況,那有可能是因為Activity的LaunchMode設置為了singleTask。5.0之后,android的LaunchMode與StartActivityForResult的關系發生了一些改變。兩個Activity,A和B,現在由A頁面跳轉到B頁面,看一下LaunchMode與StartActivityForResult之間的關系:

before5.0.png
after5.0.png

這是為什么呢?

這是因為ActivityStackSupervisor類中的startActivityUncheckedLocked方法在5.0中進行了修改。在5.0之前,當啟動一個Activity時,系統將首先檢查Activity的launchMode,如果為A頁面設置為SingleInstance或者B頁面設置為singleTask或者singleInstance,則會在LaunchFlags中加入FLAG_ACTIVITY_NEW_TASK標志,而如果含有FLAG_ACTIVITY_NEW_TASK標志的話,onActivityResult將會立即接收到一個cancle的信息,而5.0之后這個方法做了修改,修改之后即便啟動的頁面設置launchMode為singleTask或singleInstance,onActivityResult依舊可以正常工作,也就是說無論設置哪種啟動方式,StartActivityForResult和onActivityResult()這一組合都是有效的。所以如果你目前正好基于5.0做相關開發,不要忘了向下兼容,這里有個坑請注意避讓。

總結

實際開發過程中如果采用比較合理的Activity啟動模式來做好任務棧的管理,可以事半功倍。在launchMode的選擇上首先要搞清楚當前的Activity的作用,以及實際使用場景來做出合理選擇。關于Activity任務棧的相關知識,短短一篇文章也很難涵蓋的全,如果想了解更多相關知識,可以去有心課堂(stay4it.com)看我的視頻課程。

本文參考閱讀:

Android開發藝術探索
Android內核剖析
Android群英傳
http://droidyue.com/blog/2015/08/16/dive-into-android-activity-launchmode/
http://blog.csdn.net/dliyuedong/article/details/47172143

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

推薦閱讀更多精彩內容