保存和恢复 Android Fragment 的状态
经过几年在 Android 应用开发中应用 Fragment 的努力之后,我必须要说尽管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());
}
@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
当一个 fragment 从后退栈中返回时(Fragment A就是在这种情况),Fragment A 中视图将会遵循下图的 Fragment 生命周期被重新创造出来。
你将会看到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 时,旋转屏幕两次
当你旋转屏幕一次,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"));
}
...
}
任何的评论和建议都很欢迎!
这是本人课余时间的翻译,也是我第一次使用markdown书写,错误很多,还请耐心指出,谢谢!
原文链接请点击原文链接
保存和恢复 Android Fragment 的状态的更多相关文章
- Android Fragment使用(三) Activity, Fragment, WebView的状态保存和恢复
Android中的状态保存和恢复 Android中的状态保存和恢复, 包括Activity和Fragment以及其中View的状态处理. Activity的状态除了其中的View和Fragment的状 ...
- Android Fragment 详解(一)
Android从3.0开始引入fragment,主要是为了支持更动态更灵活的界面设计,比如在平板上的应用.平板机上拥有比手机更大的屏幕空间来组合和交互界面组件们.Fragment使你在做那样的设计时, ...
- Android Fragment详解(四):管理Fragment
要管理fragment们,需使用FragmentManager,要获取它,需在activity中调用方法getFragmentManager(). 你可以用FragmentManager来做以上事情: ...
- Android Fragment——详细解释
1.Fragment概述 在一个Activity中. Fragment代表UI的一个部分或者一个行为.一个Activity能够结合多个Fragment对象,也能够在多个activity中使用同样Fra ...
- Android使用Fragment来实现ViewPager的功能(解决切换Fragment状态不保存)以及各个Fragment之间的通信
以下内容为原创,转载请注明:http://www.cnblogs.com/tiantianbyconan/p/3364728.html 我前两天写过一篇博客<Android使用Fragment来 ...
- Android课程---Activity中保存和恢复用户状态
onSaveInstanceState 保存 在暂停之后和保存之前调用 onRestoreInstanceState 恢复 再启动之后和显示之前调用 package com.example.chens ...
- 【转】android笔记--保存和恢复activity的状态数据
一般来说, 调用onPause()和onStop()方法后的activity实例仍然存在于内存中, activity的所有信息和状态数据不会消失, 当activity重新回到前台之后, 所有的改变都会 ...
- 【Android】11.2 通过重写对应的方法保存和恢复实例的状态
分类:C#.Android.VS2015: 创建日期:2016-02-21 一.简介 通过重写(也叫回调)对应的方法来管理Activity的生命周期,比如用户旋转屏幕时应用程序要能自动保存和恢复实例的 ...
- Android——保存和恢复用户状态
onSaveInstanceState 保存 在暂停之后和保存之前调用 onRestoreInstanceState 恢复 再启动之后和显示之前调用 package com.example.chens ...
随机推荐
- 光场相机重聚焦之三——Matlab光场工具包使用、重聚焦及多视角效果展示
这一小节说一下Matlab光场工具包的使用,展示重聚焦和多视角的效果. 从Lytro illum中导出的raw数据为.lfp格式的光场图像文件(约52M大小),该文件包含以下几部分:光场图像数据raw ...
- Centos7完全分布式搭建Hadoop2.7.3
(一)软件准备 1,hadoop-2.7.3.tar.gz(包) 2,三台机器装有cetos7的机子 (二)安装步骤 1,给每台机子配相同的用户 进入root : su root ---------& ...
- stm32的定时器学习
看了几篇博客之后,对这个定时器也有了一些认识,其实和51差不多,就是配置定时器的时候多了几个步骤而已. 其中很好的一片是:http://blog.sina.com.cn/s/blog_49cb4249 ...
- 【小瑕疵】在div里插入img后在底部留有缝隙怎么解决
[本文转载自http://blog.sina.com.cn/s/blog_9fd5b6df01013mld.html] 图片IMG与容器下边界之间有空隙怎么办?这里介绍3中简单的解决方法. 第一,给图 ...
- Cf #353 D. Tree Construction
题目链接:http://codeforces.com/problemset/problem/675/D 题目大意是将一个没有相同数字的数列中的数字依次插入到二叉搜索树中,问除了第一个数字以外,其他数字 ...
- HDU 5617 DP
沿着一条条斜线DP即可,dp[k][i][j]表示第k步,一端在第j列,另一端在第i列,构成回文的个数,沿着四个方向推下去即可. #include <iostream> #include ...
- mySql 安装教程
看了好久别人的文章,今天就开始自己写第一篇.希望给别人能提供帮助,也可以方便自己查阅. 前两天自己安装了mysql,感觉是比oracle好装多了. mysql安装有两种方式,一种是安装包安装方式,一种 ...
- WebService应用--使用java开发WebService程序
使用Eclipse开发第一个WebService程序,本示例采用的工具为Spring-Tool-Suite,和Eclipse没有本质的区别,开发环境jdk1.7 一.开发步骤: 1.新建名为WebSe ...
- centeOS6.5 RPM方式安装MySQL5.6
RPM方式安装MySQL5.6 a. 检查MySQL及相关RPM包,是否安装,如果有安装,则移除(rpm –e 名称) 1 [root@localhost ~]# rpm -qa | grep -i ...
- Idea+maven+tomcat部署第一个tomcat项目
IDEA创建Maven项目及部署发布,IDEA配置Tomcat,创建java源文件夹. 此教程适合刚刚使用IDEA的新手. 工具/原料 IntelliJ IDEA 2016.3.4 apache- ...