Activity生命周期和啟動模式

本篇博客是筆者看過《Android開發藝術探索》才寫的,有些是借鑒了此本書的內容,當有些內容進行了精進。

Activity生命周期和啟動模式

1.Activity的生命周期全面分析

1.1 典型情況下的Activty生命周期分析

??典型情況下的Activity的生命周期在基礎階段你已經學過啦,,我們再來回顧一次,這次就整清楚些,把Activity生命周期的每一個過程弄清楚:

Actvvity生命周期圖

1.啟動了一個Activity,通常是Intent來完成。啟動一個Activity首先要執行的回調函數是onCreate(),通常在代碼中你需要在此函數中綁定布局,綁定控件,初始化數據等做一些初始化的工作。

2.即將執行Activity的onStart()函數,執行之后Activity已經可見,但是還沒有出現在前臺,無法與用戶進行交互。這個時候通常Activity已經在后臺準備好了,但是就差執行onResume()函數出現在前臺。

3.即將執行Activity的onResume()函數,執行之后Activity不止可見而且還會出現在前臺,可以與用戶進行交互啦。

4.由于Activity執行了onResume()函數,所以Activity出現在了前臺。也就是Activity處于運行狀態。

5.處于運行狀態的Activity即將執行onPause()函數,什么情況下促使Activity執行onPause()方法呢?
?[1]啟動了一個新的Activity
?[2]返回上一個Activity
?可以理解為當需要其他Activity,當前的Activity必須先把手頭的工作暫停下來,再來把當前的界面空間交給下一個需要界面的Activity,而onPause()方法可以看作是一個轉接工作的過程,因為屏幕空間只有那么一個,每次只允許一個Activity出現在前臺進行工作。通常情況下onPause()函數不會被單獨執行,執行完onPause()方法后會繼續執行onStop()方法,執行完onStop()方法才真正意味著當前的Activity已經退出前臺,存在于后臺。

6.Activity即將執行onStop()函數,在“5”中已經說得很清楚了,當Activity要從前臺切換至后臺的時候會執行,比如:用戶點擊了返回鍵,或者用戶切換至其他Activity等。

7.當前的Activity即將執行onDestory()函數,代表著這個Activity即將進入生命的終結點,這是Activity生命周期中的最后一次回調生命周期,我們可以在onDestory()函數中,進行一些回收工作和資源的釋放工作,比如:廣播接收器的注銷工作等。

8.執行完onDestory()方法的Activity接下來面對的是被GC回收,宣告生命終結。

9.很少情況下Activity才走“9”,網上一些關于對話框彈出后Activity會走“9”的說法,經過筆者驗證,在某個Activity內彈出對話框并沒有走“9”,所以網上大部分這樣說法的文章要么是沒驗證,要么直接轉載的,這個例子說明,實驗出真知,好了,不廢話了,那么什么情況下,Activity會走“9”呢?看看下面這位博主才是真的懂得“實驗出真知”的人:

http://blog.csdn.net/a872822645/article/details/62217965

10.當用戶在其他的Activity或者桌面回切到這個Activity時,這個Activity就會先去執行onRestart()函數,Restart有“重新開始”的意思,然后接下來執行onStart()函數,接著執行onResume()函數進入到運行狀態。

11.在“10”中講的很清楚了。

12.高優先級的應用急需要內存,此時處于低優先級的此應用就會被kill掉。

13.用戶返回原Activity。

下面來著重說明一下Activity每個生命周期函數:

onCreate():

?表示Activity正在被創建,這是Activity生命周期的第一個方法。通常我們程序員要在此函數中做初始化的工作,比如:綁定布局,控件,初始化數據等。

onStart():

?表示Activity正在被啟動,這時候的Activity已經被創建好了,完全過了準備階段,但是沒有出現在前臺,需要執行onResume()函數才可以進入到前臺與用戶進行交互。

onResume():

?表示Activitiy已經可見了,并且Activity處于運行狀態,也就是Activity不止出現在了前臺,而且還可以讓用戶點擊,滑動等等操作與它進行交互。

onPause():

?表示Activity正在暫停,大多數情況下,Activity執行完onPause()函數后會繼續執行onStop()函數,造成這種函數調用的原因是當前的Activity啟動了另外一個Activity或者回切到上一個Activity。還有一種情況就是onPause()函數被單獨執行了,并沒有附帶執行onStop()方法,造成這種函數調用的原因很簡單,就是當前Activity里啟動了類似于對話框的東東。

onStop():

?表示Activity即將停止,我們程序員應該在此函數中做一些不那么耗時的輕量級回收操作。

onRestart():

?表示Activity正在重新啟動。一般情況下,一個存在于后臺不可見的Activity變為可見狀態,都會去執行onRestart()函數,然后會繼續執行onStart()函數,onResume()函數出現在前臺并且處于運行狀態。

onDestory():

?表示Activity要被銷毀了。這是Activity生命中的最后一個階段,我們可以在onDestory()函數中做一些回收工作和資源釋放等,比如:廣播接收器的注銷等。

1.2 異常情況下的Activity生命周期分析

??對于正常情況下的Activity生命周期,你應該是比較清楚了,通常情況下,Activity會碰到一些異常的情況,如果你沒對這些異常作合理的邏輯處理,某些情形下會讓用戶給你開發的應用給差評。最嚴重的情況是用戶在使用應用寫了一篇字數相當之多的文章,某個操作突然讓當前的Activity發生異常,如果你不把用戶辛苦半天寫的文章數據保存下來,這個用戶肯定會罵爹的,而且會在應用市場給你的應用來波差評加吐槽,所以異常的邏輯處理是非常重要的,所以接下來,我們來學習一下異常情況下的Activity生命周期,學完后,你將會知道各種異常情況下如何作相應的邏輯處理了。

情況1:資源相關的系統配置發生改變導致Activity被殺死并重新創建

??了解這個問題之前,首先你得對Android資源加載機制有足夠的了解:
??Android資源加載機制鏈接:http://blog.csdn.net/thesingularityisnear/article/details/51581311

??在Activity系統配置發生改變之際,如果你沒對Activity做任何處理,Activity就會被銷毀并且重新創建,這種情況下Activity會發生以下重建過程:

異常情況下Activity的重建過程(Android開發藝術探索)

??,可以從圖中看出當Activity發生意外的情況的時候,這里的意外指的就是系統配置發生改變,Activity會被銷毀,其onPause,OnStop,onDestory函數均會被調用,同時由于Actiivty是在異常情況下終止的,系統會調用onSaveInstanceState來保存當前Activity狀態。調用onSaveInstanceState的時機總會發生在onStop之前,至于會不會調用時機發生在onPause方法之前,那就說不定了,這個沒有固定的順序可言,正常情況下一般onSaveInstanceState不會被調用。當Activity被重新創建后,系統會調用onRestoreInstanceState,并且把Actiivty銷毀時onSaveInstanceState方法所保存的Bundle對象作為參數傳遞給onRestoreInstanceState和onCreate方法。所以我們可以通過onRestoreInstanceState和onCreate方法來判斷Actiivty是否被重建了,如果被重建了,那么我們就可以取出之前保存的數據并恢復,從時序上來看,onRestoreInstanceState的調用時機發生在onStart之后。

??同時,在onSaveInstanceState和onRestoreInstanceState方法中,系統自動為我們做了一定的恢復工作。當Activity在異常情況下需要重新創建時,系統會默認為我們保存當前Activity的視圖結構。當Activity在異常情況下需要重新創建時,系統會默認為我們保存當前Activity的視圖結構,并且在Activity重啟后為我們恢復這些數據,比如:文本框中用戶輸入的數據,ListView滾動的位置等,這些View相關的狀態系統都能夠默認為我們恢復。具體針對某一個特定的View系統 能為我們恢復哪些數據,我們可以查看View的源碼。和Activity一樣,每個View都有onSaveInstanceState和onRestoreInstanceState這兩個方法,看一下它們的具體實現,就能知道系統能夠自動為每個View恢復哪些數據。

關于保存和恢復View層次結構,系統的工作流程是這樣的:

??首先Activity被意外終止時,Activity會調用onSaveInstanceState去保存數據,然后Activity會委托Window去保存數據,接著Window在委托它上面的頂級容器去保存數據。頂級容器是一個ViewGroup,一般來說它很可能是DecorView。最后頂層容器再去一一通知它的子元素來保存數據,這樣整個數據保存過程就完成了。可以發現,這是一個典型的委托思想,上層委托下層,父容器去委托子元素去處理一件事情,這種思想在Android中有很多應用,比如:View的繪制過程,事件分發等都是采用類似的思想。至于數據恢復過程也是類似的,這樣就不再重復介紹了。

情況2:資源內存不足導致低優先級的Activity被殺死

??首先,Activity有優先級?你肯定懷疑,代碼中都沒設置過啊!優先級從何而來,其實這里的Activity的優先級是指一個Activity對于用戶的重要程度,比如:正在與用戶進行交互的Activity那肯定是最重要的。我們可以按照重要程度將Activity分為以下等級:

優先級最高: 與用戶正在進行交互的Activity,即前臺Activity。

優先級中等:可見但非前臺的Activity,比如:一個彈出對話框的Activity,可見但是非前臺運行。

優先級最低:完全存在與后臺的Activity,比如:執行了onStop。

??當內存嚴重不足時,系統就會按照上述優先級去kill掉目前Activity所在的進程,并在后續通過onSaveInstanceState和onRestoreInstanceState來存儲和恢復數據。如果一個進程中沒有四大組件的執行,那么這個進程將很快被系統殺死,因此,一些后臺工作不適合脫離四大組件獨立運行在后臺中,這樣進程更容易被殺死。比較好的方法就是將后臺工作放入Service中從而保證進程有一定的優先級,這樣就不會輕易地被系統殺死。

  • 總結:
    上面分析了系統的數據存儲和恢復機制,我們知道,當系統配置發生改變之后,Activity會被重新創建,那么有沒有辦法不重新創建呢?答案是有的,接下來我們就來分析這個問題。系統配置中有很多內容,如果某項內容發生了該變后,我們不想系統重新創建Activity可以給Activity指定configChanges屬性。比如我們不想讓Actiivty在屏幕旋轉的時候重新創建,就可以給configChanges屬性添加orientation這個值,如下所示。

    android:configChanges = "orientation"

??如果我們指定多個值,那么就用“|”連接起來即可,下表是configChanges的屬性含義表:

configChanges的屬性含義表

2.Activity的啟動模式全面分析

??Activity的啟動模式,你在初學期間一定很熟悉了吧!不管你是否熟悉還是不熟悉,跟隨筆者的思路把Activity的啟動模式整理一遍:

問題1:Activity為什么需要啟動模式?
問題2:Activity的啟動模式有哪些?特性如何
問題3:如何給Activity選擇合適的啟動模式

問題1:Activity為什么需要啟動模式?

??我們都知道啟動一個Activity后,這個Activity實例就會被放入任務棧中,當點擊返回鍵的時候,位于任務棧頂層的Activity就會被清理出去,當任務棧中不存在任何Activity實例后,系統就回去回收這個任務棧,也就是程序退出了。這只是對任務棧的基本認識,深入學習,筆者會在之后文章中提到。那么問題來了,既然每次啟動一個Activity就會把對應的要啟動的Activity的實例放入任務棧中,假如這個Activity會被頻繁啟動,那豈不是會生成很多這個Activity的實例嗎?對內存而言這可不是什么好事,明明可以一個Activity實例就可以應付所有的啟動需求,為什么要頻繁生成新的Activity實例呢?杜絕這種內存的浪費行為,所以Activity的啟動模式就被創造出來去解決上面所描述的問題。

問題2:Activity的啟動模式有哪些?特性如何

??Activity的啟動模式有4種,分別是:standard,singleTop,singleTask和singleInstance。下面一一作介紹:

1.系統默認的啟動模式:standard
??標準模式,這也是系統的默認模式。每次啟動一個Activity都會重新創建一個新的實例,不管這個實例是否存在。被創建的實例的生命周期符合典型情況下的Activity的生命周期。在這種模式下,誰啟動了這個Activity,那么這個Activity就運行在啟動它的那個Activity的任務棧中。比如Activity A啟動了Activity B(B是標準模式),那么B就會進入到A所在的任務棧中。有個注意的地方就是當我們用ApplicationContext 去啟動standard模式的Activity就會報錯,這是因為standard模式的Actiivty默認會進入啟動它的Activity所屬的任務棧中,但是由于非Activity類型的Context(如ApplicationContext)并沒有所謂的任務棧,所以這就會出現錯誤。解決這個問題的方法就是為待啟動的Activity指定FLAG_ACTIVITY_NEW_TASK標記位,這樣啟動的時候就會為它創建一個新的任務棧,這個時候啟動Activity實際上以singleTask模式啟動的,讀者可以自己仔細體會。

2.棧頂復用模式:singleTop
??在這種模式下,如果新的Activity已經位于任務棧的棧頂,那么此Activity不會被重新創建,同時它的onNewIntent方法被回調,通過此方法的參數我們可以取出當前請求的信息。需要注意的是,這個Activity的onCreate,onStart不會被系統調用,因為它并沒有發生改變。如果新的Activity已經存在但不是位于棧頂,那么新的Activity仍然會重新重建。舉個例子,假設目前棧內的情況為ABCD,其中ABCD為四個Activity,A位于棧低,D位于棧頂,這個時候假設要再次啟動D,如果D的啟動模式為singleTop,那么棧內的情況依然為ABCD;如果D的啟動模式為standard,那么由于D被重新創建,導致棧內的情況為ABCDD。

3.棧內復用模式:singleTask
??這是一種單例實例模式,在這種模式下,只要Activity在一個棧中存在,那么多次啟動此Activity都不會重新創建實例,和singleTop一樣,系統也會回調其onNewIntent。具體一點,當一個具有singleTask模式的Activity請求啟動后,比如Activity A,系統首先尋找任務棧中是否已存在Activity A的實例,如果已經存在,那么系統就會把A調到棧頂并調用它的onNewIntent方法,如果Activity A實例不存在,就創建A的實例并把A壓入棧中。舉幾個栗子:

  • 比如目前任務棧S1的情況為ABC,這個時候Activity D以singleTask模式請求啟動,其所需的任務棧為S2,由于S2和D的實例均不存在,所以系統會先創建任務棧S2,然后再創建D的實例并將其投入到S2任務棧中。
  • 另外一種情況是,假設D所需的任務棧為S1,其他情況如同上面的例子所示,那么由于S1已經存在,所以系統會直接創建D的實例并將其投入到S1。
  • 如果D所需的任務棧為S1,并且當前任務棧S1的情況為ADBC,根據棧內復用的原則,此時D不會重新創建,系統會把D切換到棧頂并調用其onNewIntent方法,同時由于singleTask默認具有clearTop的效果,會導致棧內所有在D上面的Activity全部出棧,于是最終S1中的情況為AD。

??通過以上3個例子,你應該能比較清晰地理解singleTask的含義了。

4.單實例模式:singleInstance
??這是一種加強的singleTask模式,它除了具有singleTask模式所有的特性外,還加強了一點,那就是具有此種模式的Activity只能單獨位于一個任務棧中,換句話說,比如Activity A是singleInstance模式,當A啟動后,系統會為它創建一個新的任務棧,然后A獨自在這個新的任務棧中,由于棧內復用的特性,后續的請求均不會創建新的Activity,除非這個獨特的任務棧被系統銷毀了。

  • 總結
    上面介紹了4種啟動模式,這里需要指出一種情況,我們假設目前有2個任務棧,前臺任務棧的情況為AB,而后臺任務棧的情況為CD,這里假設CD的啟動模式均為singleTask。現在請求啟動D,那么整個后臺任務棧都會被切換到后臺,這個時候整個后退列表變成了ABCD。當用戶按back鍵的時候,列表中的Activity會一一出棧,如下圖1所示:
圖1 任務棧演示圖1

??如果不是請求的D而是請求的C,那么情況就不一樣了,如下圖2所示:

圖2 任務棧演示圖2

??如何指定活動的啟動模式呢?在AndroidManifest.xml文件當注冊活動的代碼中去指定
比如:我要把MainActivity活動的啟動模式指定為singleInstance模式

設置Activity的啟動模式代碼截圖

Activity的Flags

??Activity的Flags有很多,這里筆者就不羅嗦了,直接貼一個鏈接:

http://www.cnblogs.com/xgjblog/p/3994990.html

3.IntentFilter的匹配規則

??在初學期間,你已經知道Activity的啟動分為隱式和顯式,那么什么是顯式啟動一個Activitiy,什么又是隱式啟動一個Activity呢?顯式啟動一個Activity需要明確地指定被啟動對象的組件信息,包含包名和類名,顯式調用目的性太強,滿足組件信息的只有那么一個Activity類而已,而隱式調用需要Intent能夠匹配目標的IntentFilter中所設置的過濾信息,如果不匹配將無法啟動目標Activity。IntentFilter中的過濾信息有action,category,data。原則上一個Intent是不應該同時擁有隱式和顯式的Activity配置信息,但是如果同時都有的情況下啟動的應該是顯式的啟動。下面是一個過濾規則的示例:

//此處來個截圖

??為了匹配過濾列表,需要同時匹配過濾列表的action,category,data信息,否則匹配失敗。一個過濾列表中的action,category,data可以有多個,所有的action,category,data分別構成不同類別,同一類別的信息共同約束當前類別的匹配過程。只有一個Intent同時匹配action類別,category類別,data類別才算完全匹配,只有完全匹配才能成功啟動目標Activitry。另外一點,一個Activity可以有多個intent-filter規則,組合你應該知道哈,反是有多個action,category,data的都會有多個匹配規則,比如:假設一個Activity的action有3個,category有4個,data有5個,那么這個Activity的匹配規則會有多少個呢?345個咯,是不是很簡單呢。下面我們就來分別詳細講講action,category,data的匹配規則。

3.1 action的匹配規則:

??action是一個字符串,系統預定義一些action,同時我們也可以在應用種定義自己的action。action的匹配規則是Intent中的action必須能夠和過濾規則中的action匹配,這里說的是指action的字符串值完全一樣。一個過濾規則中可以有多個action,那么只要Intent中的action能夠和過濾規則中的任何一個action相同即可匹配成功。另外,action是區分大小寫,大小寫不同字符串相同的action會匹配失敗。

3.2 category的匹配規則:

??category是一個字符串,系統預定義了一些category,同時我們也可以在應用中定義自己的category。category的匹配規則和action不同,它要求Intent中如果含有category,那么所有的category都必須和過濾規則中的其中一個categroy相同。換句話說,Intent中如果出現了category,不管有幾個category,對于每個category來說,它必須是過濾規則中已經定義的category。當然,Intent可以沒有category,如果沒有category的話,Intent仍然可以匹配成功的,說到這里你可能會覺得這不科學啊,其實這個匹配規則理解起來很簡單,就是高中的集合思想,就是判斷Intent中的category構成的集合是否為啟動Activity的category過濾規則構成集合的子集,這很簡單吧!但是注意與action的匹配規則相區別,action是要求Intent必須有一個action且必須能夠和過濾規則中的某個action相同,這不就是交集的意思嗎?瞬間你就會懂得了action的匹配規則其實就是集合的交集思想,而category的匹配規則其實就是集合的子集思想。總結來說:對于action的匹配規則是這樣的,Intent中的action所構成的集合和Activity過濾規則中action構成的集合有交集的話才能匹配上,而對于category的匹配規則是這樣的,Intent中的category所構成的集合是Activity過濾規則中category構成的集合的子集能匹配上。其實很簡單action-->交集,categroy-->子集。

3.3 data的匹配規則:

??data的匹配規則和action相似,如果過濾規則中定義了data,那么Intent中必須也要定義可匹配的data。在介紹data的匹配規則之前,我們需要先了解一下data的結構:

data的結構:
??data的語法如下所示:

<data android:scheme="string"
      android:host="string"
      android:port="string"
      android:path="string"
      android:pathPattern="string"
      android:pathprefix="string"
      android:mimeType="string"/>

??data由兩部分組成,mimeType和URI。mimeType指媒體類型,比如:image/jpeg,audio/mpeg4-generic和vedio/*等,可以表示圖片,文本,視頻等不同的媒體格式,而URI中包含的數據就比較多了,下面是URI的結構:

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

推薦閱讀更多精彩內容