執行個體探究Android開發中Fragment狀態的儲存與恢複方法_Android

來源:互聯網
上載者:User

我們都知道,類似 Activity, Fragment 有 onSaveInstanceState() 回調用來儲存狀態。

在Fragment裡面,利用onSaveInstanceState儲存資料,並可在onActivityCreated裡面恢複資料。

public void onActivityCreated(Bundle savedInstanceState) {  super.onActivityCreated(savedInstanceState);  ...  if (savedInstanceState != null) {    // Restore the fragment's state here  }}public void onSaveInstanceState(Bundle outState) {  super.onSaveInstanceState(outState);  // Save the fragment's state here}

但是,根據作者的經驗,這個方法調用非常的不靠普。Fragment 在旋轉螢幕和返回堆棧(Backstack)中的時候,都會建立一個全新的 View,這個 onSaveInstanceState() 方法經常會出現不會被調用的情況,導致 Fragment 的狀態丟失。

我們來通過接下來的執行個體尋找解決方案。

首先,儘管已經有了一個類似 Activity 中的 onSaveInstanceState 方法,但是它顯然不能覆蓋所有情況。換種說法就是,你不能僅僅依賴於 onSaveInstanceState 方法來儲存/恢複視圖的狀態。這裡有一些案例研究。

案例1:只有一個 Fragment 在棧中時,旋轉螢幕

旋轉螢幕是用來測試執行個體狀態的儲存/恢複的最簡單的案例。這種情況很容易處理,你僅僅需要簡單地儲存一些東西,比如:成員變數,它也會在旋轉螢幕時在 onSaveInstanceState 丟失,在 onActivityCreated 或者 onViewStateRestored 中恢複,如下所示:

int someVar;@Override  protected void onSaveInstanceState(Bundle outState) {  outState.putInt("someVar", someVar);  outState.putString(“text”, tv1.getText().toString());}@Overridepublic void onActivityCreated(Bundle savedInstanceState) {  super.onActivityCreated(savedInstanceState);  someVar = savedInstanceState.getInt("someVar", 0);  tv1.setText(savedInstanceState.getString(“text”));}

看上去是不是很好?不過也不是全不管用。這種情況是在 onSaveInstanceState 不被回調,但是視圖重建。這意味著什嗎?UI 裡的所有東西都沒了。下面就是這種案例。

案例2:後退棧(Back Stack)中的 Fragment


當一個 fragment 從後退棧中返回時(Fragment A就是在這種情況),Fragment A 中視圖將會遵循下圖的 Fragment 生命週期被重新創造出來。

你將會看到Fragment從後退棧中返回時,會回調 onDestroyview 方法和 onCreateview 方法。不管怎樣,顯然在這種情況 onSaveInstanceState 方法沒有被調用。結果就是 UI 裡的所有都沒有了,然後預設按照 Layout XML 檔案中定義的來重新建立。

不管怎樣,實現了內在檢視狀態儲存的視圖,如:帶有 android:freeezeText 的 EditText 或者 TextView,仍然能夠儲存好視圖的狀態,這是因為 Fragment 實現了對內在視圖的狀態儲存,但我們這些開發人員不能抓住這些事件。我們唯一能做的就是在 onDestroyView 方法中手動儲存執行個體狀態。

@Overridepublic void onSaveInstanceState(Bundle outState) {  super.onSaveInstanceState(outState);  // Save State Here}@Overridepublic void onDestroyView() {  super.onDestroyView();  // Save State Here}

問題也隨之而來,onDestroyView 不提供任何協助來儲存執行個體狀態到一個 Bundle,那我們應該把這些執行個體狀態儲存到什麼地方呢? 答案就是 Argument, 它會隨著 Fragment 一直儲存著。

現在代碼如下所示:

Bundle savedState;@Overridepublic void onActivityCreated(Bundle savedInstanceState) {  super.onActivityCreated(savedInstanceState);  // Restore State Here  if (!restoreStateFromArguments()) {   // First Time running, Initialize something here  }}@Overridepublic void onSaveInstanceState(Bundle outState) {  super.onSaveInstanceState(outState);  // Save State Here  saveStateToArguments();}@Overridepublic void onDestroyView() {  super.onDestroyView();  // Save State Here  saveStateToArguments();}private void saveStateToArguments() {  savedState = saveState();  if (savedState != null) {   Bundle b = getArguments();   b.putBundle(“internalSavedViewState8954201239547”, savedState);  }}private boolean restoreStateFromArguments() {  Bundle b = getArguments();  savedState = b.getBundle(“internalSavedViewState8954201239547”);  if (savedState != null) {   restoreState();   return true;  }  return false;}/////////////////////////////////// Restore Instance State Here/////////////////////////////////private void restoreState() {  if (savedState != null) {   // For Example   //tv1.setText(savedState.getString(“text”));  }}//////////////////////////////// Save Instance State Here//////////////////////////////private Bundle saveState() {  Bundle state = new Bundle();  // For Example  //state.putString(“text”, tv1.getText().toString());  return state;}

你能夠容易地在 saveState 儲存你的 fragment 的狀態,在 restoreState 恢複狀態。現在已經看起來好多了不少。我們已經快結束了,但是還有一種怪異的情況。

案例3:在後退棧中超過一個 fragment 時,旋轉螢幕兩次

當你旋轉螢幕一次,onSaveInstanceState 會被回調,正如我們所期待的,UI 的狀態會被儲存。但是當你旋轉螢幕超過一次,上述的代碼可能導致應用的崩潰。原因就是儘管當你旋轉螢幕時, onSaveInstanceState 方法被調用,但是在後退棧中的 fragment 會完全銷毀視圖,直到你瀏覽返回到原來那個 fragment 才會重新建立。因此,你再次旋轉螢幕,就沒有視圖來儲存狀態。當你試圖訪問那些不存在的視圖,saveState() 將會導致 NullPointerException,從而使應用崩潰。

方法就是檢查在 fragment 中視圖是否存在。如果存在那就儲存,如果不存在,那就在 Argument 中 savedState 不需要儲存,然後返回時儲存。或者我們甚至不需要做任何事,因為在Argument 中已經做好了。

private void saveStateToArguments() {  if (getView() != null)   savedState = saveState();  if (savedState != null) {   Bundle b = getArguments();   b.putBundle(“savedState”, savedState);  }}

哈,現在全都解決了!

Fragment 最終模版
如下就是我現在用於我工作中的 fragment 模版。

import android.os.Bundle;import android.support.v4.app.Fragment;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import com.inthecheesefactory.thecheeselibrary.R;/** *Created by nuuneoi on 11/16/2014. */public class StatedFragment extends Fragment {  Bundle savedState;  public StatedFragment() {    super();  }  @Override  public void onActivityCreated(Bundle savedInstanceState) {    super.onActivityCreated(savedInstanceState);    // Restore State Here    if (!restoreStateFromArguments()) {      // First Time, Initialize something here      onFirstTimeLaunched();    }  }  protected void onFirstTimeLaunched() {  }  @Override  public void onSaveInstanceState(Bundle outState) {    super.onSaveInstanceState(outState);    // Save State Here    saveStateToArguments();  }  @Override  public void onDestroyView() {    super.onDestroyView();    // Save State Here    saveStateToArguments();  }  ////////////////////  // Don't Touch !!  ////////////////////  private void saveStateToArguments() {    if (getView() != null)      savedState = saveState();    if (savedState != null) {      Bundle b = getArguments();      b.putBundle("internalSavedViewState8954201239547", savedState);    }  }  ////////////////////  // Don't Touch !!  ////////////////////  private boolean restoreStateFromArguments() {    Bundle b = getArguments();    savedState = b.getBundle("internalSavedViewState8954201239547");    if (savedState != null) {      restoreState();      return true;    }    return false;  }  /////////////////////////////////  // Restore Instance State Here  /////////////////////////////////  private void restoreState() {    if (savedState != null) {      // For Example      //tv1.setText(savedState.getString("text"));      onRestoreState(savedState);    }  }  protected void onRestoreState(Bundle savedInstanceState) {  }  //////////////////////////////  // Save Instance State Here  //////////////////////////////  private Bundle saveState() {    Bundle state = new Bundle();    // For Example    //state.putString("text", tv1.getText().toString());    onSaveState(state);    return state;  }  protected void onSaveState(Bundle outState) {  }}

如果你使用這個模版,僅僅只需簡單地繼承這個類StatedFragment,然後在 onSaveState() 中儲存事物,在 onRestoreState() 中恢複它們。上述代碼就會為你做好剩下的工作,我相信這已經覆蓋了我已知的可能情況。

setRetainInstance 能夠協助開發人員在布局改變時(如:旋轉螢幕)處理成員變數 而你可能注意到我沒有設定 setRetaionInstance 為 true。請記住,這就是我的目的,因為setRetainInstance(true)並沒有覆蓋全部的情況。最主要的原因是你不能一次又一次地儲存那些經常背使用的嵌套 fragment 。所以我建議就是不要儲存執行個體,除非你100%確定這個 fragment 不會用於嵌套。

用法:
好訊息。這個部落格講述的 StateFragment 現在加入了一個非常容易使用的庫,現在已經在 jcenter 上發布了。現在你可以簡單地在你工程的 build.gradle 檔案中加上一個依賴。如下所示:

dependencies {  compile 'com.inthecheesefactory.thecheeselibrary:stated-fragment-support-v4:0.9.1'}

繼承 StateFragment ,然後在 onSaveState(Bundle outState) 中儲存狀態,在 onRestoreState(Bundle saveInstanceState)中恢複狀態。如果你想在 fragment 第一次啟動時做點什麼的話,你也可以覆蓋 onFirstTimeLaunched() 方法(在之後不會被調用)。

public class MainFragment extends StatedFragment {  ...  /**   * Save Fragment's State here   */  @Override  protected void onSaveState(Bundle outState) {    super.onSaveState(outState);    // For example:    //outState.putString("text", tvSample.getText().toString());  }  /**   * Restore Fragment's State here   */  @Override  protected void onRestoreState(Bundle savedInstanceState) {    super.onRestoreState(savedInstanceState);    // For example:    //tvSample.setText(savedInstanceState.getString("text"));  }  ...}

End

最後,不要忽略狀態儲存的問題,在記憶體不足或者系統限制比較苛刻的機器上面,都有可能出現Fragment或Activity被回收,比如經常出現拍照之後返回App,但App裡面的資料被清空了,更常見的就是旋轉螢幕,所以要儲存好自己需要的資料。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.