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

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

任務棧是什么

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

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

任務棧的作用

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

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

standard

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

standard.png

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

singleTop

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

2(1).png

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

singleTask

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

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

singleTask1.png

2:假如DActivity啟動所需要的任務棧為T2,由于T2和D的實例均不存在,那么系統(tǒng)會先創(chuàng)建任務棧T2,然后再創(chuàng)建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,根據(jù)棧內(nèi)復用的原則,此時D不會重新創(chuàng)建,系統(tǒng)會把D切換到棧頂并調(diào)用其onNewIntent()方法,同時由于singleTask默認具有ClearTop的效果,會導致棧內(nèi)所有在D上面的Activity全部出棧,于是最終T3的情況為AD。

singleTask3.png

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

singleTask4.png

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

singleTask5.png

singleInstance

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

范冰冰.pic.jpg

設置Intent的Flag

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

FLAG_ACTIVITY_NEW_TASK

使用一個新的Task來啟動一個Activity,但啟動的每個Activity都講在一個新的Task中。該Flag通常使用在從Service中啟動Activity的場景,由于Service中并不存在Activity棧,所以使用該Flag來創(chuàng)建一個新的Activity棧,并創(chuàng)建新的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

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

before5.0.png
after5.0.png

這是為什么呢?

這是因為ActivityStackSupervisor類中的startActivityUncheckedLocked方法在5.0中進行了修改。在5.0之前,當啟動一個Activity時,系統(tǒng)將首先檢查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做相關開發(fā),不要忘了向下兼容,這里有個坑請注意避讓。

總結(jié)

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

本文參考閱讀:

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容