当前位置: 移动技术网 > 移动技术>移动开发>Android > 实例探究Android开发中Fragment状态的保存与恢复方法

实例探究Android开发中Fragment状态的保存与恢复方法

2019年07月24日  | 移动技术网移动技术  | 我要评论
我们都知道,类似 activity, fragment 有 onsaveinstancestate() 回调用来保存状态。 在fragment里面,利用onsaveins

我们都知道,类似 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 在栈中时,旋转屏幕

201642795611832.jpeg (900×450)

屏幕旋转是用来测试实例状态的保存/恢复的最简单的案例。这种情况很容易处理,你仅仅需要简单地保存一些东西,比如:成员变量,它也会在屏幕旋转时在 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”));
}

看上去是不是很好?不过也不是全不管用。这种情况是在 onsaveinstancestate 不被回调,但是视图重新生成。这意味着什么?ui 里的所有东西都没了。下面就是这种案例。

案例2:后退栈(back stack)中的 fragment

201642795642363.jpeg (900×450)
当一个 fragment 从后退栈中返回时(fragment a就是在这种情况),fragment a 中视图将会遵循下图的 fragment 生命周期被重新创造出来。

201642795709760.png (317×847)

你将会看到fragment从后退栈中返回时,会回调 ondestroyview 方法和 oncreateview 方法。不管怎样,显然在这种情况 onsaveinstancestate 方法没有被调用。结果就是 ui 里的所有都没有了,然后默认按照 layout xml 文件中定义的来重新创建。

不管怎样,实现了内在视图状态保存的视图,如:带有 android:freeezetext 的 edittext 或者 textview,仍然能够保存好视图的状态,这是因为 fragment 实现了对内在视图的状态保存,但我们这些开发者不能抓住这些事件。我们唯一能做的就是在 ondestroyview 方法中手动保存实例状态。

@override
public void onsaveinstancestate(bundle outstate) {
  super.onsaveinstancestate(outstate);
  // save state here
}
@override
public void ondestroyview() {
  super.ondestroyview();
  // save state here
}

问题也随之而来,ondestroyview 不提供任何帮助来保存实例状态到一个 bundle,那我们应该把这些实例状态保存到什么地方呢? 答案就是 argument, 它会随着 fragment 一直保存着。

现在代码如下所示:

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;
}
/////////////////////////////////
// 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 时,旋转屏幕两次

201642795821161.jpeg (1118×282)

当你旋转屏幕一次,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里面的数据被清空了,更常见的就是旋转屏幕,所以要保存好自己需要的数据。

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网