Android的狀態(tài)保存和恢復(fù)

Activity的狀態(tài)保存和恢復(fù)

Activity的狀態(tài)什么時候需要保存和恢復(fù)

Activity的銷毀一般分為兩種情況:

  1. 當用戶按返回按鈕或你的Activity通過調(diào)用finish()銷毀時,這屬于正常銷毀,此時是不需要恢復(fù)狀態(tài)的,因為下次回來又是重新創(chuàng)建新的實例。
  2. 如果Activity當前被停止或長期未使用,或者前臺Activity需要更多資源以致系統(tǒng)必須關(guān)閉后臺進程恢復(fù)內(nèi)存,系統(tǒng)也可能會銷毀Activity,這屬于非正常銷毀,盡管Activity實例被銷毀,但系統(tǒng)會保存其狀態(tài),這樣,如果用戶導(dǎo)航回該Activity,系統(tǒng)會使用保存了該Activity被銷毀時的狀態(tài)數(shù)據(jù)來創(chuàng)建Activity的新實例。

屏幕旋轉(zhuǎn)、鍵盤可用性改變、 語言改變都可以歸結(jié)為第二種情況;值得一提的是,如果需要模擬這種情況的Activity銷毀,可以打開開發(fā)者選項,選擇不保留活動(英文為Do not keep activities),即可模擬內(nèi)存不足時的系統(tǒng)行為。

保存和恢復(fù)Activity狀態(tài)

什么時候調(diào)用Activity的onSaveInstanceState()

  1. 屏幕旋轉(zhuǎn)重建會調(diào)用onSaveInstanceState()
  2. 啟動另一個activity: 當前activity在離開前會調(diào)用onSaveInstanceState()
  3. 按Home鍵的情形和啟動另一個activity一樣, 當前activity在離開前會onSaveInstanceState()

什么時候不調(diào)用Activity的onRestoreInstanceState()

  1. 屏幕旋轉(zhuǎn)重建會調(diào)用onRestoreInstanceState()
  2. 啟動另一個activity,返回時如果因為被系統(tǒng)殺死需要重建, 則會從onCreate()重新開始生命周期, 調(diào)用onRestoreInstanceState()
  3. 按Home鍵的情形和啟動另一個activity一樣,用戶再次點擊應(yīng)用圖標返回時, 如果重建發(fā)生, 則會調(diào)用onCreate()和onRestoreInstanceState()

什么時候都不調(diào)用

  1. 用戶主動finish()掉的activity不會調(diào)用onSaveInstanceState(), 包括主動按back退出的情況.
  2. 新建的activity, 從onCreate()開始, 不會調(diào)用onRestoreInstanceState().
basic-lifecycle-savestate.png

當你的Acivity開始被停止時,系統(tǒng)會調(diào)用onSaveInstanceState()方法,以便你的Activity可以使用鍵值對集合來保存狀態(tài)信息。此方法默認實現(xiàn)了自動保存有關(guān)Activity的視圖層次結(jié)構(gòu)的狀態(tài)信息,例如EditText中的文本信息或ListView的滾動位置。為了保存其他狀態(tài)信息,你必須在onSaveInstanceState()方法中將鍵值對添加到Bundle對象。例如:

public class MainActivity extends Activity {
    static final String SOME_VALUE = "int_value";
    static final String SOME_OTHER_VALUE = "string_value";

    @Override
    protected void onSaveInstanceState(Bundle savedInstanceState) {
        // Save custom values into the bundle
        savedInstanceState.putInt(SOME_VALUE, someIntValue);
        savedInstanceState.putString(SOME_OTHER_VALUE, someStringValue);
        // Always call the superclass so it can save the view hierarchy state
        super.onSaveInstanceState(savedInstanceState);
    }
}

系統(tǒng)會在Activity被銷毀前調(diào)用上述方法。然后系統(tǒng)會調(diào)用onRestoreInstanceState方法,我們可以從bundle中恢復(fù)狀態(tài):

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);
    // Restore state members from saved instance
    someIntValue = savedInstanceState.getInt(SOME_VALUE);
    someStringValue = savedInstanceState.getString(SOME_OTHER_VALUE);
}

您可以選擇實現(xiàn)系統(tǒng)在 onStart() 方法之后調(diào)的 onRestoreInstanceState(),而不是在 onCreate()期間恢復(fù)狀態(tài)。系統(tǒng)只在存在要恢復(fù)的已保存狀態(tài)時調(diào)用 onRestoreInstanceState(),因此你無需檢查 Bundle是否為 null,實例狀態(tài)也可以在ActivityonCreate()方法中恢復(fù),但是onRestoreInstanceState()方法中是最方便的。這確保所有的初始化已經(jīng)完成,并允許子類來決定是否使用默認實現(xiàn)。

注意:onSaveInstanceState和onRestoreInstanceState不能保證一起調(diào)用。當有可能銷毀活動的可能性時,Android調(diào)用onSaveInstanceState()。 但是,有些情況下調(diào)用onSaveInstanceState,但不會銷毀活動,因此不會調(diào)用onRestoreInstanceState。

保存和恢復(fù)Fragment狀態(tài)

Fragments也有onSaveInstanceState()方法:

public class MySimpleFragment extends Fragment {
    private int someStateValue;
    private final String SOME_VALUE_KEY = "someValueToSave";
   
    // Fires when a configuration change occurs and fragment needs to save state
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putInt(SOME_VALUE_KEY, someStateValue);
        super.onSaveInstanceState(outState);
    }
}

然后,我們可以從onCreateView中保存的狀態(tài)中取出數(shù)據(jù):

public class MySimpleFragment extends Fragment {
   // ...

   // Inflate the view for the fragment based on layout XML
   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.my_simple_fragment, container, false);
        if (savedInstanceState != null) {
            someStateValue = savedInstanceState.getInt(SOME_VALUE_KEY);
            // Do something with value if needed
        }
        return view;
   }
}

為了正確保存Fragments狀態(tài),我們需要確保在配置更改時避免不必要地重新創(chuàng)建Fragments。在配置更改后,在Activity中初始化的任何Fragments都需要通過tag進行查找:

public class ParentActivity extends AppCompatActivity {
    private MySimpleFragment fragmentSimple;
    private final String SIMPLE_FRAGMENT_TAG = "myfragmenttag";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (savedInstanceState != null) { // saved instance state, fragment may exist
           // look up the instance that already exists by tag
           fragmentSimple = (MySimpleFragment)  
              getSupportFragmentManager().findFragmentByTag(SIMPLE_FRAGMENT_TAG);
        } else if (fragmentSimple == null) { 
           // only create fragment if they haven't been instantiated already
           fragmentSimple = new MySimpleFragment();
        }
    }
}

注意:這就需要我們在使用事務(wù)將Fragment放入Activity中要包含一個用于查找的標記。

public class ParentActivity extends AppCompatActivity {
    private MySimpleFragment fragmentSimple;
    private final String SIMPLE_FRAGMENT_TAG = "myfragmenttag";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // ... fragment lookup or instantation from above...
        // Always add a tag to a fragment being inserted into container
        if (!fragmentSimple.isInLayout()) {
            getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.container, fragmentSimple, SIMPLE_FRAGMENT_TAG)
                .commit();
        }
    }
}
fragmentstatesaving.gif

245086265-57bc01e4c0a4a_articlex.gif

對于Fragment來說,有一些特殊情況不同于Activity,我覺得你需要知道這些情況。一旦Fragment從后退棧中返回,它的View 會被銷毀,并重新創(chuàng)建

fragment-lifecycle.png

在這種情況下,F(xiàn)ragment并不會被銷毀,只有Fragment中的View 會被銷毀。 結(jié)果是,并不會發(fā)生任何實例狀態(tài)的保存。但是上面展示的這些View在Fragment生命周期中被重新創(chuàng)建時,發(fā)生了什么?

在這種情況下,F(xiàn)ragment中的View 狀態(tài)的保存/恢復(fù)會被內(nèi)部調(diào)用。結(jié)果就是,每一個實現(xiàn)了內(nèi)部View 狀態(tài)保存/恢復(fù)的View ,將會被自動的保存并且恢復(fù)狀態(tài),例如帶有android:freezeText="true"屬性的EditText或者TextView。就之前完美顯示的一樣。

注意,在這種情況下,只有View 被銷毀(并重建)了。Fragment仍然在這里,就像它內(nèi)部的成員變量一樣。所以你不需要對它們做任何事情。

View的狀態(tài)保存和恢復(fù)

注意: 每一個你需要開啟View狀態(tài)保存和恢復(fù)的View設(shè)置android:id屬性,不然它們的狀態(tài)不能正確的恢復(fù)。

ListView

通常當你旋轉(zhuǎn)屏幕時,應(yīng)用程序?qū)G失屏幕上列表的滾動位置和其他狀態(tài)。要正確保留ListView的狀態(tài),可以在onPause()中存儲實例狀態(tài)并在onViewCreated中恢復(fù)狀態(tài),如下所示:

// YourActivity.java
private static final String LIST_STATE = "listState";
private Parcelable mListState = null;

// Write list state to bundle
@Override
protected void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);
    mListState = getListView().onSaveInstanceState();
    state.putParcelable(LIST_STATE, mListState);
}

// Restore list state from bundle
@Override
protected void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
    mListState = state.getParcelable(LIST_STATE);
}


@Override
protected void onResume() {
    super.onResume();
    loadData(); // make sure data has been reloaded into adapter first
    // ONLY call this part once the data items have been loaded back into the adapter
    // for example, inside a success callback from the network
    if (mListState != null) {
        myListView.onRestoreInstanceState(mListState);
        mListState = null;
    }
}

注意:在調(diào)用onRestoreInstanceState之前,必須把數(shù)據(jù)源加載到適配器中,換句話說就是直到從網(wǎng)絡(luò)或數(shù)據(jù)庫加載數(shù)據(jù)之后,再讓ListView調(diào)用onRestoreInstanceState。

RecyclerView

與ListView類似:

// YourActivity.java
public final static int LIST_STATE_KEY = "recycler_list_state";
Parcelable listState;

protected void onSaveInstanceState(Bundle state) {
     super.onSaveInstanceState(state);
     // Save list state
     listState = mLayoutManager.onSaveInstanceState();
     state.putParcelable(LIST_STATE_KEY, mListState);
}

protected void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
    // Retrieve list state and list/item positions
    if(state != null)
        listState = state.getParcelable(LIST_STATE_KEY);
}

@Override
protected void onResume() {
    super.onResume();
    if (mListState != null) {
        mLayoutManager.onRestoreInstanceState(listState);
    }
}

處理運行時變更

有些設(shè)備配置可能會在運行時發(fā)生變化(例如屏幕方向、鍵盤可用性及語言)。 發(fā)生這種變化時,Android 會重啟正在運行的 Activity(先后調(diào)用 onDestroy()onCreate())。重啟行為旨在通過利用與新設(shè)備配置匹配的備用資源自動重新加載您的應(yīng)用,來幫助它適應(yīng)新配置。
要妥善處理重啟行為,Activity 必須通過常規(guī)的Activity 生命周期恢復(fù)其以前的狀態(tài),在 Activity 生命周期中,Android 會在銷毀 Activity 之前調(diào)用 onSaveInstanceState(),以便你保存有關(guān)應(yīng)用狀態(tài)的數(shù)據(jù)。 然后,你可以在 onCreate()onRestoreInstanceState()期間恢復(fù) Activity 狀態(tài)。
要測試應(yīng)用能否在保持應(yīng)用狀態(tài)完好的情況下自行重啟,您應(yīng)該在應(yīng)用中執(zhí)行各種任務(wù)時調(diào)用配置變更(例如,更改屏幕方向)。 您的應(yīng)用應(yīng)該能夠在不丟失用戶數(shù)據(jù)或狀態(tài)的情況下隨時重啟,以便處理如下事件:配置發(fā)生變化,或者用戶收到來電并在應(yīng)用進程被銷毀很久之后返回到應(yīng)用。

解決方案

你可能會遇到這種情況:重啟應(yīng)用并恢復(fù)大量數(shù)據(jù)不僅成本高昂,而且給用戶留下糟糕的使用體驗。 在這種情況下,您有兩個其他選擇:

  1. 在配置變更期間保留對象允許 Activity 在配置變更時重啟,但是要將有狀態(tài)對象傳遞給 Activity 的新實例。

  2. 自行處理配置變更阻止系統(tǒng)在某些配置變更期間重啟 Activity,但要在配置確實發(fā)生變化時接收回調(diào),這樣,您就能夠根據(jù)需要手動更新 Activity。

在配置變更期間保留對象

當 Android 系統(tǒng)因配置變更而關(guān)閉 Activity 時,不會銷毀您已標記為要保留的 Activity 的片段。 您可以將此類片段添加到 Activity 以保留有狀態(tài)的對象。
要在運行時配置變更期間將有狀態(tài)的對象保留在片段中,請執(zhí)行以下操作:

  1. 擴展 Fragment類并聲明對有狀態(tài)對象的引用。
  2. 在創(chuàng)建Fragment后調(diào)用 setRetainInstance(boolean)
  3. 將Fragment添加到 Activity。
  4. 重啟 Activity 后,使用 FragmentManager檢索片段。

例如,按如下方式定義片段:

public class RetainedFragment extends Fragment {

    // data object we want to retain
    private MyDataObject data;

    // this method is only called once for this fragment
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    public void setData(MyDataObject data) {
        this.data = data;
    }

    public MyDataObject getData() {
        return data;
    }
}

注意:盡管你可以存儲任何對象,但是切勿傳遞與 Activity綁定的對象,例如,DrawableAdapter、View或其他任何與 Context關(guān)聯(lián)的對象。否則,它將泄漏原始 Activity 實例的所有視圖和資源。 (泄漏資源意味著應(yīng)用將繼續(xù)持有這些資源,但是無法對其進行垃圾回收,因此可能會丟失大量內(nèi)存。)

然后,使用 FragmentManager將片段添加到 Activity。在運行時配置變更期間再次啟動 Activity 時,您可以獲得片段中的數(shù)據(jù)對象。 例如,按如下方式定義 Activity:

public class MyActivity extends Activity {

    private RetainedFragment dataFragment;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // find the retained fragment on activity restarts
        FragmentManager fm = getFragmentManager();
        dataFragment = (DataFragment) fm.findFragmentByTag(“data”);

        // create the fragment and data the first time
        if (dataFragment == null) {
            // add the fragment
            dataFragment = new DataFragment();
            fm.beginTransaction().add(dataFragment, “data”).commit();
            // load the data from the web
            dataFragment.setData(loadMyData());
        }

        // the data is available in dataFragment.getData()
        ...
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // store the data in the fragment
        dataFragment.setData(collectMyLoadedData());
    }
}

在此示例中,onCreate()
添加了一個片段或恢復(fù)了對它的引用。此外,onCreate()還將有狀態(tài)的對象存儲在片段實例內(nèi)部。onDestroy()對所保留的片段實例內(nèi)的有狀態(tài)對象進行更新。

自行處理配置變更

如果應(yīng)用在特定配置變更期間無需更新資源,并且因性能限制您需要盡量避免重啟,則可聲明 Activity 將自行處理配置變更,這樣可以阻止系統(tǒng)重啟 Activity。

:自行處理配置變更可能導(dǎo)致備用資源的使用更為困難,因為系統(tǒng)不會為您自動應(yīng)用這些資源。 只能在您必須避免 Activity 因配置變更而重啟這一萬般無奈的情況下,才考慮采用自行處理配置變更這種方法,而且對于大多數(shù)應(yīng)用并不建議使用此方法。

要聲明由 Activity 處理配置變更,請在清單文件中編輯相應(yīng)的 <activity>
元素,以包含 android:configChanges
屬性以及代表要處理的配置的值。android:configChanges
屬性的文檔中列出了該屬性的可能值(最常用的值包括 "orientation"和 "keyboardHidden"
,分別用于避免因屏幕方向和可用鍵盤改變而導(dǎo)致重啟)。

例如,以下清單文件代碼聲明的 Activity 可同時處理屏幕方向變更和鍵盤可用性變更:

<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboardHidden"
          android:label="@string/app_name">

現(xiàn)在,當其中一個配置發(fā)生變化時,MyActivity不會重啟。相反,MyActivity會收到對 onConfigurationChanged()的調(diào)用。向此方法傳遞Configuration對象指定新設(shè)備配置。您可以通過讀取 Configuration中的字段,確定新配置,然后通過更新界面中使用的資源進行適當?shù)母?。調(diào)用此方法時,Activity 的 Resources對象會相應(yīng)地進行更新,以根據(jù)新配置返回資源,這樣,您就能夠在系統(tǒng)不重啟 Activity 的情況下輕松重置 UI 的元素。

注意:從 Android 3.2(API 級別 13)開始,當設(shè)備在縱向和橫向之間切換時,“屏幕尺寸”也會發(fā)生變化。因此,在開發(fā)針對 API 級別 13 或更高版本(正如 minSdkVersion
targetSdkVersion
屬性中所聲明)的應(yīng)用時,若要避免由于設(shè)備方向改變而導(dǎo)致運行時重啟,則除了 "orientation"
值以外,您還必須添加 "screenSize"
值。 也就是說,您必須聲明 android:configChanges="orientation|screenSize"
。但是,如果您的應(yīng)用面向 API 級別 12 或更低版本,則 Activity 始終會自行處理此配置變更(即便是在 Android 3.2 或更高版本的設(shè)備上運行,此配置變更也不會重啟 Activity)。

例如,以下 onConfigurationChanged() 實現(xiàn)檢查當前設(shè)備方向:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

Configuration 對象代表所有當前配置,而不僅僅是已經(jīng)變更的配置。大多數(shù)時候,您并不在意配置具體發(fā)生了哪些變更,而且您可以輕松地重新分配所有資源,為您正在處理的配置提供備用資源。 例如,由于 Resources 對象現(xiàn)已更新,因此您可以通過 setImageResource() 重置任何 ImageView,并且使用適合于新配置的資源(如提供資源中所述)。

參考資料

http://www.cnblogs.com/mengdd/p/4528417.html

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

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

  • Android中的狀態(tài)保存和恢復(fù) Android中的狀態(tài)保存和恢復(fù), 包括Activity和Fragment以及其...
    圣騎士wind閱讀 5,163評論 4 48
  • Activity是一個應(yīng)用組件,用戶可與其提供的屏幕進行交互。以執(zhí)行撥打電話,拍攝照片,發(fā)送電子郵件或查看地圖等操...
    DanieX閱讀 1,161評論 0 4
  • Activity 是一個應(yīng)用組件,用戶可與其提供的屏幕進行交互,以執(zhí)行撥打電話、拍攝照片、發(fā)送電子郵件或查看地圖等...
    RxCode閱讀 1,175評論 0 0
  • Activity https://developer.android.com/guide/components/a...
    XLsn0w閱讀 767評論 0 4
  • 從懷孕前到現(xiàn)在算來近兩年沒上班了,單位經(jīng)歷很多變化,整合后,我一直沒有崗位,生了二寶在家?guī)抻终埣倭?,三八?jié)公司發(fā)...
    小滿xm閱讀 170評論 0 0

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