本篇博客是筆者看過《Android開發藝術探索》才寫的,有些是借鑒了此本書的內容,當有些內容進行了精進。
Activity生命周期和啟動模式
1.Activity的生命周期全面分析
1.1 典型情況下的Activty生命周期分析
??典型情況下的Activity的生命周期在基礎階段你已經學過啦,,我們再來回顧一次,這次就整清楚些,把Activity生命周期的每一個過程弄清楚:
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發生意外的情況的時候,這里的意外指的就是系統配置發生改變,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的屬性含義表:
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所示:
??如果不是請求的D而是請求的C,那么情況就不一樣了,如下圖2所示:
??如何指定活動的啟動模式呢?在AndroidManifest.xml文件當注冊活動的代碼中去指定
比如:我要把MainActivity活動的啟動模式指定為singleInstance模式
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>