Android 之 Activity 生命週期的淺析(二)

shine_zejian發表於2016-08-09

上一篇文章,我們主要分析了Activity的正常情況下生命週期及其方法,本篇主要涉及內容為Activity的異常情況下的生命週期。

Activity異常生命週期

異常的生命週期是指Activity被系統回收或者當前裝置的Configuration發生變化(一般指橫豎屏切換)從而導致Activity被銷燬重建。異常的生命週期主要分以下兩種情況:

  • 1、相關的系統配置發生改變導致Activity被殺死並重新建立(一般指橫豎屏切換)
  • 2、記憶體不足導致低優先順序的Activity被殺死

1、相關的系統配置發生改變導致Activity被殺死並重新建立(一般指橫豎屏切換)

我們先來分析第一種情況,當Activity處於豎屏狀態,如果突然旋轉螢幕,由於系統配置發生了變化,在預設的情況下,Activity會被銷燬並重新建立,當然我們可以人為干預來防止這種情況。在這裡首先我們使用上一篇文章的測試程式碼來驗證此過程,程式碼如下:

程式碼比較簡單,我們重寫了onSaveInstanceState方法和onRestoreInstanceState方法,這兩個方法後面我們會詳細介紹,這裡先看看執行結果:

20160717152441641

從Log中我們可以看出當我們正常啟動Activity時,onCreate,onStart,onResume方法都會依次被回撥,而如果我們此時把豎屏的Activity人為的調整為橫屏,我們可以發現onPause,onSaveInstanceState,onStop,onDestroy,onCreate,onStart,onRestoreInstanceState,onResume依次被呼叫,單從呼叫的方法我們就可以知道,Activity先被銷燬後再重新建立,其異常生命週期如下:

20160717152515688

現在異常生命週期的流程我們大概也就都明白,但是onSaveInstanceState和onRestoreInstanceState方法是幹什麼用的呢?實際上這兩個方法是系統自動呼叫的,當系統配置發生變化後,Activity會被銷燬,也就是onPause,onStop,onDestroy會被依次呼叫,同時因為Activity是在異常情況下銷燬的,android系統會自動呼叫onSaveInstanceState方法來儲存當前Activity的狀態資訊,因此我們可以在onSaveInstanceState方法中儲存一些資料以便Activity重建之後可以恢復這些資料,當然這個方法的呼叫時機必須在onStop方法之前,也就是Activity停止之前。至跟onPause方法的呼叫時機可以隨意。而通過前面的Log資訊我們也可以知道當Activity被重新建立之後,系統還會去呼叫onRestoreInstanceState方法,並把Activity銷燬時通過onSaveInstanceState方法儲存的Bundle物件作為引數同時傳遞給onRestoreInstanceState和onCreate方法,因此我們可以通過onRestoreInstanceState和onCreate方法來判斷Activity是否被重新建立,倘若被重建了,我們就可以對之前的資料進行恢復。從Log資訊,我們可以看出onRestoreInstanceState方法的呼叫時機是在onStart之後的。這裡有點需要特別注意,onSaveInstanceState和onRestoreInstanceState只有在Activity異常終止時才會被呼叫的,正常情況是不會呼叫這兩個方法的。

到這裡大家可能還有一個疑問,onRestoreInstanceState和onCreate方法都可以進行資料恢復,那到底用哪個啊?其實兩者都可以,兩者的區別在於,onRestoreInstanceState方法一旦被系統回撥,其引數Bundle一定不為空,無需額外的判斷,而onCreate的Bundle卻不一定有值,因為如果Activity是正常啟動的話,Bundle引數是不會有值的,因此我們需要額外的判斷條件,當然雖說兩者都可以資料恢復,但更傾向於onRestoreInstanceState方法。
最後還有點我們要知道的是,在onSaveInstanceState方法和onRestoreInstanceState方法中,android系統會自動幫我們恢復一定的資料,如當前Activity的檢視結構,文字框的資料,ListView的滾動位置等,這些View相關的狀態系統都會幫我們恢復,這是因為每個View也有onSaveInstanceState方法和onRestoreInstanceState方法,我們來測試一個例子,在EditText文字框中輸入資料然後切換橫豎螢幕,結果如下:

20160717152623272

20160717152555672

由此可以知道,系統確實幫我們恢復了Activity結構和View的相關資料。

2、記憶體不足導致低優先順序的Activity被殺死

接下來我們來聊聊記憶體不足導致低優先順序的Activity被殺死,然後重建,其實也不用聊了,其資料的儲存過程和恢復過程跟上面的情況基本沒差。我們還是繼續聊聊Activity被殺死的情況吧,當系統記憶體不足的時候,系統就會按照一定的優先順序去殺死目標Acitivity的程式來回收記憶體,並且此時Activity的onSaveInstanceState方法會被呼叫來儲存資料,並在後續Activity恢復時呼叫onRestoreInstanceState方法來恢復資料,所以為了Activity所在程式儘量不被殺死,我們應該儘量讓其保持高的優先順序。那什麼樣的優先順序較高呢?為了更深入瞭解這個問題,我們先來進入一個新的話題,Android 程式層次。

Android 程式層次

在android系統中最重要的程式被稱為前臺程式,然後依次是任何可見程式、服務程式、後臺程式,最後是空程式。接下來我們將進一步展開。
在開始之前我們先要明確一個問題,當我們談論程式優先順序的時候是以 activity、service 這樣的元件來說的,但請注意這些優先順序是在程式的級別上的,而非元件級別。只要有一個元件是前臺程式,就會將整個程式變為前臺程式。同時我們要知道絕大多數應用是單程式的,如果我們有生命週期差異很大的不同部分或者某個部分非常重量型,那麼我們強烈建議把它們分為不同的程式,以便讓重量級程式儘早被系統回收。
明白這點後我們再看看下面一張圖(圖來自Who lives and who dies? Process priorities on Android):

20160717152655960

從圖中我們可以看出共有5中優先順序執行緒,Foreground Processes ,Visible Processes ,Service Processes , Background Processes , Empty Processes ;

  • 1.Foreground Processes(前臺程式)

系統中前臺程式的數量很少(這點從圖中也是可以看出來的), 前臺程式幾乎不會被殺死. 只有當記憶體低到無法保證所有的前臺程式同時執行時才會選擇殺死某個前臺程式.以下幾種都屬於前臺程式:
a. 處於前臺正與使用者互動的activity
b. 與前臺activity繫結的service
c. 呼叫了startForeground()方法的service
d. 正在執行onCreate(), onStart(), 或onDestroy()方法的service
e. 正在執行onReceive()方法的BroadcastReceiver.
凡是包含以上任意一種情況的程式都是前臺程式。當然一些 activity 在依靠他們成為前臺程式的同事,也可能依賴 bound service 。任何程式,如果它持有一個繫結到前臺 activity 的服務,那麼它也被賦予了同樣的前臺優先順序。

  • 2.Visible Processes(可視程式)

此時如果一個Activity可見但並非處於前臺時,如在Activity中彈出了一個對話方塊,從而導致Activity可見但位於後臺無法與使用者互動,這個程式就可以被視為可見程式,同時我們也必須明白可見 activity 的 bound service 和 content provider 也處於可見程式狀態。這同樣是為了保證使用中的 activity 所依賴的程式不會被過早地殺掉。但還是需要注意的是,只是可見並不意味著不能被殺掉。如果來自前臺程式的記憶體壓力過大,可見程式仍有可能被殺掉。從使用者的角度看,這意味著當前 activity 背後的可見 activity 會被黑屏代替。當然,倘若我們正確地重建 activity ,在前臺 activity 關閉之後,我們的程式和 activity 會立刻恢復而沒有資料損失。

  • 3.Service Processes(服務程式)

如果我們通過startService()啟動一個service服務,那麼它被看作是一個服務程式。對於許多在後臺做處理(如非同步載入資料,獲取耗時資源等)而沒有立即成為前臺服務的app都屬於這種情況。這是比較常見也是最合理的後臺處理方式,這種程式只有在可見程式和前臺程式記憶體不足時才有可能被殺掉。

  • 4.Background Processes(後臺程式)

假如我們的Activity目前是前臺程式,但是這時候,我們點Home鍵,將導致onPause,onStop方法被呼叫,我們的程式也就變成了後臺程式,當然我們的後臺程式並不會被立馬殺死,所以這些程式會保留一段時間,直到更高優先順序程式需要記憶體的時候才被回收,並且是按照最近最少使用順序(最少使用的會被優先回收)。很多時候我們會發現手機的記憶體大都是被後臺App程式佔用了大部分空間,而android系統這樣做的好處是可以使用我們在下次重新開啟此程式的app時無需重新分配和載入資源,從而擁有更好的使用者體驗。
。然而記憶體不足的時候,他們仍然會被殺掉,所以我們應該和可見 activity 處理情況一樣,應該儘量能夠在不丟失使用者狀態的情況下重建這些 activity ,以便達到更佳的使用者體驗。

  • 5.Empty Processes(空程式)

在任何層次中,空程式都是最低優先順序的。如果我們的程式不屬於以上類別,那它就是空程式。空程式是沒有活躍的元件,只是出於快取的目的而被保留(為了更加有效地使用記憶體而不是完全釋放掉),只要 Android 系統記憶體需要可以隨時殺掉它們。

嗯,現在我們應該對android程式有個比較明確的概念了,回到之前的的Activity記憶體不足被android系統殺死的話題,為了防止一些重要的Activity不被意外殺死,我們應該讓當前所在程式的Activity保持較高的優先順序,如使其變為前臺程式,或者通過service去繫結,也可以讓其成為單獨的程式。當然如果真的不幸被殺死,我們也應該儘量通過onSaveInstanceState方法和onRestoreInstanceState方法來儲存和恢復資料,以便獲得更佳的使用者體驗。

解決Activity銷燬重建問題

通過上面的分析我們知道當系統配置發生變化後,Activity會被重建,那有沒有辦法使其不重建呢?方法自然是有的,那就是我們可以給Activity指定configChange屬性,當我們不想Activity在螢幕旋轉後導致銷燬重建時,可以設定configChange=“orientation”;當SDK版本大於13時,我們還需額外新增一個“screenSize”的值,對於這兩個值含義如下:
orientation:螢幕方向發生變化,配置該引數可以解決橫豎屏切換時,Activity重建問題(API<13)
screenSize:當裝置旋轉時,螢幕尺寸發生變化,API>13後必須配置該引數才可以保證橫豎切換不會導致Activity重建。
說白了就是設定了這兩個引數後,當橫豎屏切換時,Activity不會再重建並且也不會呼叫之前相關的方法,取而代之的是回撥onConfigurationChanged方法。案例程式碼如下:

在MainActivity中我們重寫onConfigurationChanged,原型如下:

我們執行程式,然後執行橫豎屏切換,看看列印的Log資訊:

20160717152811658

從Log可以看到Activity確實沒有重建,並且也沒有回撥onSaveIntanceState方法和RestoreInstanceState方法來儲存或者恢復資料,相反的是onConfigurationChanged方法被回撥了,因此我們在這種情況下,可以在此方法中做一些額外的工作。好了,本篇就聊到這裡吧。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

Android 之 Activity 生命週期的淺析(二)

相關文章