Android中Activity執行restart過程中涉及到的四種資料儲存恢復的方法

孫群發表於2015-11-06

我們知道,當Configuration Change發生的時候(比如橫豎屏切換等),會導致Activity重啟,即先destroy,然後會restart,一般情況下restart的時間比較短,為了保證一致的使用者體驗,我們應該在Activity重啟前將一些資料儲存下來,然後在restart的時候重新根據這些資料更新UI。當然你可能想將這些資料寫到物理檔案或資料庫中,但是這樣有缺點,因為IO操作時耗時操作,會影響restart的過程,甚至導致ANR程式無響應,本文將介紹幾種將資料快取在記憶體中以便restart時進行恢復的方法。


onSaveInstanceState

Activity具有onSaveInstanceState回撥方法,如果onSaveInstanceState方法被呼叫了,那麼該回撥方法一定是在onStop之前被呼叫的,但是不能保證是在onPause之前還是之後被呼叫。一般在此處將UI持久化的一些資訊存入到Bundle中,然後在Activity重新建立執行onCreate(Bundle)或onRestoreInstanceState(Bundle)的時候再根據Bundle恢復。Activity.onSaveInstanceState的預設實現是遍歷層級結構中的所有含有id屬性的View,然後依次呼叫View.onSaveInstanceState()方法。onSaveInstanceState是android.view.View的方法,所以所有繼承自View的widget都有該方法。View.onSaveInstanceState()會儲存View自身的一些資訊,比如儲存當前選擇的哪個item等。

由於Activity.onSaveInstanceState方法只會遍歷Activity中含有id屬性的View對其UI資訊進行儲存,所以我們我們在編碼時最好給View都新增id屬性,這樣可以使其在上述方法執行的時候呼叫View的onSaveInstanceState方法,從而儲存該View的UI資訊。

如果要重寫onSaveInstanceState方法,首先要呼叫super.onSaveInstanceState(bundle)。重寫的時候不應該在此處儲存持久化的資料(比如要向SQLite資料中寫入持久資料),應該在該方法中只儲存一些與UI相關的瞬時變數,比如有時候某些成員變數也儲存著一些和UI相關的資訊,這時候就可以在onSaveInstanceState中儲存下來。
Bundle不是為儲存大量資料(比如bitmap)而設計的,並且Bundle中的資料必須經過序列化與反序列化, 因此在onSaveInstanceState不應該儲存大量資料。

onSaveInstanceState是在Activity將要被killed並且預測過段時間會重新create這樣條件下才會被呼叫。也就是說onSaveInstanceState之所以被呼叫是為了Activity在銷燬後進行重新生成進行restore的,如果Android Framework認為不滿足需要進行onSaveInstanceState的條件,那麼就不會呼叫該方法,具體來說:

  1. 如果使用者顯式地通過back鍵退出了程式,那麼不會呼叫onSaveInstanceState方法。

  2. 如果開始開啟了Activity A,然後開啟了Activity B,對A進行了部分遮罩,這時候會觸發A的onPause方法,但是Framework可能會避免呼叫A的onSaveInstanceState方法(如果在B的生命週期內A沒有被kill掉)。


利用Fragment儲存大量資料

由於onSaveInstanceState不適合儲存大量的資料,所以如果在Activity銷燬重建恢復資料的時候就不能使用該onSaveInstanceState毀掉函式存取大量資料了。

由於configuration change導致Activity銷燬的時候,Activity中標記為保留的Fragment不會銷燬,所以可以利用該特性實現存取資料,具體方法如下:

  1. 擴充套件Fragment類,並定義好相應欄位存取資料,對外暴露出設定資料和獲取資料的方法,比如setData和getData

  2. 在Fragment的onCreate方法中呼叫方法setRetainInstance(true),標記該Fragment為保留的

  3. 將該Fragment加入到Activity中

  4. 在Activity的onDestroy方法中將activity的中需要儲存的資料呼叫Fragment中上述定義的setData方法,將其儲存在Fragment中

  5. 在Activity銷燬重新生成執行onCreate的時候,重新從Fragmet中呼叫getData獲取之前儲存的資料

  6. 最後需要注意記憶體洩露的發生


考慮使用Loader快取資料

Android中可以用Loader和LoaderManager非同步獲取資料,且Loader機制有個很好的特性:可以在Activity執行restart的過程中繼續持有資料,也就是說,如果Loader一開始就已經載入了資料,那麼在Activity進行了restart之後,Activity會從Loader中立即獲取到之前快取的資料,而無需再次用LoaderManager載入資料,這樣就能實現restart之後資料恢復的功能。

如果對Loader機制不瞭解,可以參見以下兩篇博文:
Android中Loader及LoaderManager的使用(附原始碼下載)
深入原始碼解析Android中Loader、AsyncTaskLoader、CursorLoader、LoaderManager


使用靜態變數儲存資料

我們知道Java中如果一個類的成員變數是static的,那麼該static成員變數的生命週期就與該類的生命週期相同,具體來說:當Java虛擬機器載入該類的時候,就會給該類的static成員變數分配記憶體空間;當Java虛擬機器解除安裝該類的時候,該類的static成員變數的記憶體才會被回收。Android也具有該特性,假設我們的Activity中有一個static的成員變數,在Activity進行restart的過程中,Java虛擬機器沒有解除安裝掉該Activity,因為在後面的restart的過程中會用到,所以在restart過程中,該Activity的static的成員變數的記憶體沒有被回收,這樣我們就可以在restart之前往該Activity的static成員變數中寫入值,在restart之後從Activity的static成員變數中讀取值,這樣就跨restart過程持有了資料。使用該特性也要注意,在非restart導致的destroy的時候,我們需要將Activity的static成員變數賦值為null,防止記憶體洩露。

希望本文對大家有所幫助!

相關文章