文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2022-06-06 08:32

关注

我们都知道,类似 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;

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 {
  ...
  
  @Override
  protected void onSaveState(Bundle outState) {
    super.onSaveState(outState);
    // For example:
    //outState.putString("text", tvSample.getText().toString());
  }
  
  @Override
  protected void onRestoreState(Bundle savedInstanceState) {
    super.onRestoreState(savedInstanceState);
    // For example:
    //tvSample.setText(savedInstanceState.getString("text"));
  }
  ...
}

End

最后,不要忽略状态保存的问题,在内存不足或者系统限制比较苛刻的机器上面,都有可能出现Fragment或Activity被回收,比如经常出现拍照之后返回App,但App里面的数据被清空了,更常见的就是旋转屏幕,所以要保存好自己需要的数据。

您可能感兴趣的文章:android中Activity详解(生命周期、以各种方式启动Activity、状态保存,完全退出等)Android保存Activity状态的方法Android学习小结之Activity保存和恢复状态Android 保存Fragment 切换状态实例代码Android中用onSaveInstanceState保存Fragment状态的方法android初学者必须掌握的Activity状态的四大知识点(必读)Android编程实现隐藏状态栏及测试Activity是否活动的方法Android中检查、监听电量和充电状态的方法Android中监听软键盘显示状态实现代码Android编程实现状态保存的方法分析


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯