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

经过这几年使用Fragment之后,我想说,Fragment的确是一种充满智慧的设计,但是使用Fragment时有太多需要我们逐一解决的问题,尤其是在处理数据保持的时候。

首先,虽然其有类似于activity的onSaveInstanceState,但是别想仅仅靠onSaveInstanceState就能保持数据。

下面就是一些案例:

  • 情景一:stack中只有一个Fragment的时候旋转屏幕

    是的,旋转屏幕是测试数据保持最简单的方法。这种情况非常简单,你只需在onSaveInstanceState存储会在旋转的时候会丢失的数据,包括变量,然后在onActivityCreated或者onViewStateRestored中取出来:
int someVar;@Overrideprotected 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”));}

看起来很简单是吧,但是存在这样的情况,View重建,但是onSaveInstanceState未被调用,这意味着UI上的所有东西都丢失了,请看下面的案例。

  • 情景2:Fragment从回退栈的返回

    当fragment从backstack中返回(这里是Fragment A),根据 官方文档 对Fragment生命周期的描述,Fragment A中的view会重建。

    从这张图可以看到,当Fragment从回退栈中返回的时候,onDestroyView 和 onCreateView被调用,但是onSaveInstanceState貌似没有被调用,这就导致了一切UI数据都回到了xml布局中定义的初始状态。当然,那些内部实现了状态保存的view,比如有android:freezeText属性的EditText和TextView,仍然可以保持其状态,因为Fragment可以为他们保持数据,但是开发者没法获得这些事件,我们只能手动的在onDestroyView中保存这些数据。
    大概流程如下:
@Overridepublic void onSaveInstanceState(Bundle outState) {   super.onSaveInstanceState(outState);   // 这里保存数据}@Overridepublic void onDestroyView() {   super.onDestroyView();   // 如果onSaveInstanceState没被调用,这里也可以保存数据}

但是问题来了onSaveInstanceState中保存数据很简单,因为它有Bundle参数,但是onDestroyView没有,那保存在哪里呢?答案是能和Fragment一起共存的Argument。

代码大致如下:

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;}/////////////////////////////////// 取出状态数据/////////////////////////////////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的时候旋转两次

    当你旋转一次屏幕,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. SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
  2. 一句话锁定MySQL数据占用元凶
  3. Android文件存储--采用SharedPreferences保存用户偏好设置参数和
  4. java读取文本文件内容2
  5. Android(安卓)Volley 完全解析(三),定制自己的Request
  6. 视频教程-MongoDB数据库从入门到精通-Mongo DB
  7. android中-----JSON数据解析
  8. android数据共享之Content Provider(访问篇CRUD)
  9. android opengl 播放 yuv数据

随机推荐

  1. android环境搭建及改变默认avd路径
  2. [置顶] 我的Android进阶之旅------>Andro
  3. [Java][Android][Process] 暴力的服务可
  4. Android计时器TimerTask,Timer,Handler
  5. 初涉Android蓝牙开发
  6. Android(安卓)SDK 和Oralce 也有冲突
  7. Android通信之 Bluetooth
  8. android的Handler
  9. Android(安卓)Manifest.permission权限
  10. Android安全模型之Android安全机制(进程通