Activity的4種啟動模式

今天想來說說Android的啟動模式,一來自己做一個總結,二來如果能幫助到別人就更好了~

首先來看一個實際的業務場景。我之前在公司實習的時候,我所在的部門只負責一個品類(國際機票),那用戶從機票首頁開始搜索機票到最終完成訂單并支付大致分為以下幾個流程(實際流程因為考慮的問題比較多,所以要比這稍微復雜些):
  1. 用戶在機票首頁確認好出發、到達目的地、日期及人數后,就可以點擊“搜索”進入到搜索結果頁
  2. 用戶根據意愿選擇相應航班并點擊進入確認訂單頁
  3. 用戶確認好訂單內容點擊下單,開始支付,支付完成跳支付完成頁
  4. 用戶可以選擇回到首頁,或者去公共訂單系統查看訂單詳情(這屬于公共部分,不在我們討論的范圍內了)

經過上面4個步驟整個購票流程就算結束了。但是,這里有個問題,就是用戶從首頁到最后的支付完成頁之間經歷了這么多的Activity,那用戶完成了支付后想回到首頁是不是要一層一層的往回退才行?如果是這樣的話,那就太不人性化了,那怎樣才能在完成支付后一鍵回到首頁呢?有人可能會說,那就直接從支付完成頁跳轉到首頁不就可以了嗎?這樣當然是可以的,但是這樣做那之前打開的那么多Activity怎么辦呢?難道就讓它們待在Activity任務棧中嗎?這樣豈不是太浪費內存了!請看我畫的示意圖:

  

如上圖所示,如果我們在支付完成之后啟動首頁Activity那頁面1-頁面5之間的所有Activity對用戶來說都是無用的,并且占用的內存,極大地浪費這本來可能就很緊張的Android內存資源。那有沒有一些優雅的方式來解決這個問題呢? 我們可以想象一下,有沒有可能在從頁面4跳轉首頁的時候把原來處于首頁之上的所有Activity全部干掉呢?這樣不就剛好解決了我們剛剛所說浪費資源的問題了嗎?就像下圖一樣:

很顯然,無論從用戶體驗的角度還是從內存優化的角度來看第二種方式都是最為優雅的。

那怎么實現這種需求呢?有人可能會說,可以將任務棧中頁面1之上的所有Activity一層一層地執行finish()方法銷毀掉,這樣就可以回退到頁面1。這樣確實是可行的,但是有個問題:如果希望從頁面4跳轉到頁面1時傳遞一些數據回去,比如說訂單號、支付完成等信息,還需要在finish()執行前執行setResult(int resultCode, Intent intent)將要往回傳遞的數據放在Intent里面。

這樣做其實是有風險的,因為Android系統并不保證Activity任務棧中那些不可見的Activity的狀態是一直被保存著的,如果出現系統內存不足的情況,Android系統是可以回收那些處于不可見狀態的Activity的。也就是說,一旦中間有一Activity被銷毀了,那這個傳遞鏈就失去功效了。

那應該怎么辦呢?別著急,Google的Android工程師早就已經為我們想到這種需求了。那應該怎么做呢?其實就是通過設置Activity的啟動模式來實現。好,問題拋出了,下面讓我們一步一步來看。

Activity有哪幾種啟動模式?

Activity一共有4種啟動模式,分別是:

  • standard
  • singleTop
  • singleTask
  • singleInstance

下面我來分別做介紹。

一、standard

顧名思義,standard英文意思就是“標準的”。

也就是說這種啟動模式是默認的,我們平時在開發中使用最多的就是Standard模式的。

如果一個Activity的啟動模式被設置成standard,那么它可以無限制的創建。你每一次通過Intent去啟動這種模式的Activity都會重新創建一個。

大家可以想象一下郵箱里的收件箱(假設我們將打開郵件的Activity的啟動模式設置為Standard,當然這也是默認的模式)里有10封郵件。我們給查看郵件的Activity起名為CheckEmailActivity,我點擊第一封郵件將會打開一個CheckEmailActivity,當我看完之后點擊下一封郵件,另一個CheckEmailActivity又會被創建,這樣如果我們將10封郵件全部看完,那在Activity任務棧中將會有10個CheckEmailActivity,而且如果我想回到收件箱頁面還必須點10次返回鍵!想想是不是很可怕?

所以說standard模式雖然很常用,但也不是適用于任何場合。
  
另外說一點,standard模式在Android 5.0(Lollipop)之前和之后是有區別的。
  
** Android Lollipop之前**

standard模式的Activity總是會被創建在啟動它的Activity同一個任務棧中頂端(任務棧是一個棧結構,先進后出 First In Last Out),就算他們來自不同的應用。
  
想象一個場景,如果你在A應用中要分享一個本地圖片,這樣會打開系統的圖片查看應用中的圖片選擇器Activity,雖然這兩個Activity來自不同的應用,但Android系統仍將會把他們放在同一個任務棧中,即A應用的任務棧中。
  


Android Lollipop之后

如果將要啟動的Activity和啟動它的Activity來自同一個應用,那沒話說,和Lollipop之前一樣,新的Activity會被創建在當前任務棧中的頂端。

但是如果它們來自不同的應用,那就會創建一個新的任務棧,再把要啟動的Activity放在新的任務棧中,這時這個新啟動的Activity就是新創建的任務站點的根Activity。如下圖所示:

  

二、singleTop

顧名思義,singleTop的意思就是“在頂部只能有一個”。

這種啟動模式非常類似于standard,但是也有一些 區別
  
如果在啟動這種模式的Activity的時候,當前任務棧的頂端已經存在了相同的Activity,那系統就不會再創建新的,而是回調任務棧中已經存在的該Activity的onNewIntent( )方法。請看下面的示意圖:
  


  
也正因為SingleTop啟動模式的特殊性,所以在開發時,如果指定了一個Activity的啟動模式是singleTop的那就應該既要重寫onCreated()方法用于應對第一次創建的情況,也要重寫onNewIntent( )方法來應對重復創建的情況

其實大家可以想象一下,這種啟動模式的應用場景。Android既然提供了這種啟動模式,說明肯定有應有場景需要這樣的方式。其實最常用的場景就是搜索,比方說我們在搜索框中輸入想要搜索的內容點擊搜索進入SearchResultActivty(搜索結果頁)查看搜索的結果(一般我們也會在搜索結果頁提供搜索框,這樣用戶無需點擊返回鍵回到上一個頁面再在搜索框中輸入搜索內容點擊搜索),如果此時用戶還想搜點別的東西,就可以直接在當前的搜索結果頁SearchResultActivty中的搜索框輸入搜索內容繼續搜索。

大家想象一下,如果我們把SearchResultActivty的啟動模式設置為Standard的話會是什么樣的景象。比如我們連著搜了10個內容,那就會啟動10個不同的SearchResultActivty,然而這些SearchResultActivty功能完全一樣,完全沒有必要創建這么多,而且還有一個和上一節中的郵箱一樣的問題,就是用戶搜索結束想回到首頁,那就還得按10次返回鍵才能回到首頁,- -!

這時,singleTop啟動模式就派上用場了,我們首先把SearchResultActivty的啟動模式設置為singleTop,這樣用戶在SearchResultActivty頁面中繼續搜索的時候,我們只需把用戶要搜索的內容放在Intent里面然后啟動SearchResultActivty,這時系統并不會重新創建新的SearchResultActivty,而是回調當前任務棧棧頂的SearchResultActivty的onNewIntent()方法來接收帶有用戶搜索內容信息的Intent,然后我們拿到用戶搜索內容后調搜索接口,并根據接口返回內容重新刷新布局即可,似不似很神奇?其實我們在上一節提到的郵箱的問題,也是用這種方式來解決的,原理和搜索一樣的。

三、singleTask

這種啟動模式的Activity在Android系統中只允許存在一個實例

如果系統中已經存在了該種啟動模式的目標Activity,則系統并不會重新創建一個目標Activity,而是首先將持有目標Activity的整個任務棧都會被置于前臺(用戶可見),并且通過onNewIntent( )方法將啟動目標Activity的Intent傳遞給目標Activity,置于目標Activity拿到這個Intent之后要做什么操作,系統就不管了,隨便你拿來干什么,哼~。

但是這里有個問題,就是目標Activity和源Activity是不是來自同一應用。

源Activity和目標Activity來自同一個應用

這種情況還要分兩種情況說:

當前系統中還沒有目標Activity的實例
這種情況最簡單,直接在當前的任務棧中創建SingleTask模式的Activity并置于棧頂即可。

當前系統中已經存在目標Activity的實例
這種情況比較特殊,因為系統會把任務棧中目標Activity之上的所有Activity銷毀,以讓目標Activity處在棧頂的位置。

這里還要還要再提醒大家的是,因為目標Activity已經存在,系統不會重新創建,而是通過onNewIntent()的方式把Intent傳遞過來,這點和singleTop模式有些類似。注意了,這里讓我們回想一下文章開頭的我所說的場景,如何讓用戶在支付完成頁直接跳轉到首頁,并把不需要的Activity銷毀?SingleTask啟動模式是不是剛好和我們的需求一致?請看下面的示意圖:

  
源Activity和目標Activity來自不同應用

這種情況也要分兩種情況說:

當前系統中還沒有目標Activity的實例
這時系統首先會看任務管理器中是否有目標Actvity所在應用的任務棧?如果有的話,那就直接在目標Activity所在應用的任務棧的棧頂創建即可。

如果任務管理器中沒有目標Activity所在應用的任務棧,系統就會創建其所在應用的任務棧和目標Activity,并且把目標Activity作為新建任務棧的根Activity。如下圖所示:

  

** 當前系統中已經存在目標Activity的實例**
目標Activity所在任務棧會被置于前臺(即用戶可見),而且也會把目標Activity之上的所有Actvity全部銷毀。

四、singleInstance

這種啟動模式和singleTask幾乎一樣,它也只允許系統中存在一個目標Activity,包括上面我們所說的SingleTask的一些特性singleInstance都有。唯一不同的是,持有目標Activity的任務棧中只能有目標Activity一個Actvitiy,不能再有別的Activity,對! 就是承包了這個任務棧!哈哈~

其實從這種啟動模式的名字也可以看出來它表示的意思,singleInstance直譯過來就是“單一實例”,什么意思呢?這話啊有兩層意思,我來給你分析分析:1. 跟系統說,“我是獨一無二的,不許和我一樣的人存在!”,這就是說系統中存在一個目標Activity。;2. 跟任務棧說,“我是獨一無二的,不許你心里再裝別的人!”,這就是說持有目標Activity的任務棧中只能有目標Activity一個Activity。這樣說是不是好理解一些,哈哈~
  
所以,如果要啟動singleInstance模式的Activity,那只能新創建一個任務棧用來放它,因為人家說了,“我是獨一無二的!”。同樣的,如果從這種啟動模式的Activity中啟動別的Activity,那不好意思,我不管你是不是和我處在同一個應用,我所在的任務棧只能擁有我一個人,您吶,另外讓系統給你創建一個任務棧待著去吧。

好了,至此我們介紹了Activity的4種啟動模式了,也大致了解了每種啟動模式的特點了,那接下里的問題就是怎么使用呢?問題又拋出來了,好,讓我們接著往下看。

怎么使用啟動模式?

有兩種方式來使用或者說設置Activity的啟動模式:

方式1:在AndroidMenifest.xml文件中設置
  


看到沒有,在<activity>標簽中設置android:launchMode="****"屬性即可,****即我們上面所說的四種啟動模式。

方式2:通過為Intent添加標識來設置
  


看到沒有,這里使用Intent的addFlags()方法來添加一些標志,其實這個addFlags()不光可以用來設置Activity的啟動模式,還能做很多事情,它的作用是給Intent添加一些附加屬性。具體的可以參見Android api哈~
  
那我們想設置Activity的啟動模式應該給addFlags()方法設置哪些參數呢?來,接著往下看:

  • FLAG_ACTIVITY_NEW_TASK
    與"singleTask"啟動模式的作用一樣。

  • FLAG_ACTIVITY_SINGLE_TOP
    與"singleTop"啟動模式的作用一樣。

  • FLAG_ACTIVITY_CLEAR_TOP
    這個標識的意思比較特殊。它不對應于我們上面所說的啟動模式中的任何一種,我們來看一下android api中對這個標識的說明:

“如果正在啟動的 Activity 已在當前任務中運行,則會銷毀當前任務頂部的所有 Activity,并通過onNewIntent()
將此 Intent 傳遞給 Activity 已恢復的實例(現在位于頂部),而不是啟動該 Activity 的新實例。”

大家可能會發現,通過addFlags()的方式來設置啟動模式有局限性,只能顯示的設置“singleTask”和“singleTop”兩種啟動模式,而并沒有對應“standard”和“singleInstance”啟動模式的標識。是的,android api文檔中確實只只有以上三種標識用來設置啟動模式,而且第三種“**FLAG_ACTIVITY_CLEAR_TOP
**”還不對應任何一種啟動模式,難道可以算作第5種啟動模式?遺憾的是,我現在也不清楚是怎么回事。
  
但是實際開發中,我們一般都是在AndroidMenifest.xml文件中去設置Activity的啟動模式。
  
好了,文章差不多就寫到這吧。謝謝觀看~

多說一句,關于Android任務和返回棧的相關知識可以參見Android api文檔,上面說的很詳細,其實我習慣把返回棧說成是任務棧,不知道這樣是不是合適,嘿嘿~

參考文獻:
https://developer.android.com/guide/topics/manifest/activity-element.html
https://developer.android.com/guide/components/tasks-and-back-stack.html
https://inthecheesefactory.com/blog/understand-android-activity-launchmode/en

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

推薦閱讀更多精彩內容