Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android中保存和恢復Fragment狀態的最好方法

Android中保存和恢復Fragment狀態的最好方法

編輯:關於Android編程

英文原文:Probably be the best way (?) to save/restore Android Fragment’s state so far

關鍵點:Fragment的Arguments。

經過這幾年使用Fragment之後,我想說,Fragment的確是一種充滿智慧的設計,但是使用Fragment時有太多需要我們逐一解決的問題,尤其是在處理數據保持的時候。

首先,雖然其有類似於activity的onSaveInstanceState,但是別想僅僅靠onSaveInstanceState就能保持數據。

下面就是一些案例:

情景一:stack中只有一個Fragment的時候旋轉屏幕

1-kV1CcEEFC_upnM-5Mn77HA

是的,旋轉屏幕是測試數據保持最簡單的方法。這種情況非常簡單,你只需在onSaveInstanceState存儲會在旋轉的時候會丟失的數據,包括變量,然後在onActivityCreated或者onViewStateRestored中取出來:

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

看起來很簡單是吧,但是存在這樣的情況,View重建,但是onSaveInstanceState未被調用,這意味著UI上的所有東西都丟失了,請看下面的案例。

情景2:Fragment從回退棧的返回

1-FmcbQAjUusX5qY8F8N-1Iw

當fragment從backstack中返回(這裡是Fragment A),根據 官方文檔 對Fragment生命周期的描述,Fragment A中的view會重建。

1-kbK7DckgeJiBgpGFQGbcog

 

從這張圖可以看到,當Fragment從回退棧中返回的時候,onDestroyView 和 onCreateView被調用,但是onSaveInstanceState貌似沒有被調用,這就導致了一切UI數據都回到了xml布局中定義的初始狀態。當然,那些內部實現了狀態保存的view,比如有android:freezeText屬性的EditText和TextView,仍然可以保持其狀態,因為Fragment可以為他們保持數據,但是開發者沒法獲得這些事件,我們只能手動的在onDestroyView中保存這些數據。

大概流程如下:

@Override
public void onSaveInstanceState(Bundle outState) {
   super.onSaveInstanceState(outState);
   // 這裡保存數據
}
@Override
public void onDestroyView() {
   super.onDestroyView();
   // 如果onSaveInstanceState沒被調用,這裡也可以保存數據
}

但是問題來了onSaveInstanceState中保存數據很簡單,因為它有Bundle參數,但是onDestroyView沒有,那保存在哪裡呢?答案是能和Fragment一起共存的Argument

代碼大致如下:

Bundle savedState;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
   super.onActivityCreated(savedInstanceState);
   // Restore State Here
   if (!restoreStateFromArguments()) {
      // First Time running, Initialize something here
   }
}
@Override
public void onSaveInstanceState(Bundle outState) {
   super.onSaveInstanceState(outState);
   // Save State Here
   saveStateToArguments();
}
@Override
public 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;
}
/////////////////////////////////
// 取出狀態數據
/////////////////////////////////
private void restoreState() {
   if (savedState != null) {
      //比如
      //tv1.setText(savedState.getString(“text”));
   }
}
//////////////////////////////
// 保存狀態數據
//////////////////////////////
private Bundle saveState() {
   Bundle state = new Bundle();
   // 比如
   //state.putString(“text”, tv1.getText().toString());
   return state;
}

 

現在你可以輕松的在fragment的saveState和restoreState中分別存儲和取出數據了。現在看起來好多了,幾乎快要成功了,但是還有更極端的情況。

 

情景3:在回退棧中有一個以上的Fragment的時候旋轉兩次

1-UruQA80WVoyaVQGxbZYE1w

當你旋轉一次屏幕,onSaveInstanceState被調用,UI的狀態會如預期的那樣被保存,,但是當你再一次旋轉屏幕,上面的代碼就可能會崩潰。原因是雖然onSaveInstanceState被調用了,但是當你旋轉屏幕,回退棧中Fragment的view將會銷毀,同時在返回之前不會重建。這就導致了當你再一次旋轉屏幕,沒有可以保存數據的view。saveState()將會引用到一個不存在的view而導致空指針異常NullPointerException,因此需要先檢查view是否存在。如果存在保存其狀態數據,將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()中取出數據,其余的事情上面的代碼已經為你做好了,我相信覆蓋了我所知道的所有情況。

現在本文描述的StatedFragment已經被做成了一個易於使用的庫,並且發布到了jcenter,你現在只需在build.gradle中添加依賴就行了:

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

繼承StatedFragment,同時分別在onSaveState(Bundle outState)onRestoreState(Bundle savedInstanceState)中保存和取出狀態數據。如果你想在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));
    }
 
    ...
 
}

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved