参考资料:
- http://www.cnblogs.com/mengdd/p/5582244.html
-
https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en
大部分内存摘自上述博客,感谢原作者的分享;
开发中,由于状态保存这种场景需要模拟,会造成了一定的开发成本,如:内存不够时,app被回收,唤醒时,可能出现错误情况;
Activity的销毁与重建
- 正常情况:back键,与调用finish方法;
- 特殊情况:当Activity处于onStop状态时,如:退到后台,并且长时间不用时,极有可能会被系统回收,用来释放一些内存;
- 旋屏情况:如果Activity支持旋屏,每次旋屏都会导致activity的销毁与重建;
特殊情况下
当activity回到前台时,如果被回收了,此时,系统会重新创建新的Activity实例,并利用旧实例存下来的数据来恢复界面;这些数据称为:instance state,存在Bundle对象中;
缺省状态下,系统会把每一个View对象保存起来(比如EditText对象中的文本,ListView中的滚动条位置等(注意:需要提供android:id)),即如果activity实例被销毁和重建,那么不需要你编码,layout状态会恢复到前次状态。但是如果你的activity需要恢复更多的信息,比如成员变量信息,则需要自己动手写了。
在这里就涉及到回调函数onSaveInstanceState(),注意 Activity onSaveInstanceState() 有2个重载方法,一般我们使用下面的:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
系统会在用户离开activity的时候调用这个函数,并且传递给它一个Bundle object,如果系统稍后需要重建这个activity实例,它会传递同一个Bundle object到onRestoreInstanceState() 和 onCreate() 方法中去。
举个例子:当ActivityA在前台时,如果用户按下home键,或者 打开一个新的ActivityB,或来电等情形下,系统会自动调用 onSaveInstanceState()方法;
Activity - onSaveInstanceState()触发的2个情况
- 系统回收时,调用;唤醒时,执行回调onRestoreXXX;
- 用户离开Activity时,调用;唤醒时,如果系统未回收,不执行onRestoreXXX;
** 存储Activity状态**
我们就在 onSaveInstanceState() 方法中来存储状态,一定要调用super;
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString(KEY_FRAGMENT_TAG, mFragmentCurrentTag);
super.onSaveInstanceState(outState);
}
恢复Activity状态
当被回收唤醒时,会执行 onCreate() 和onRestoreInstanceState()回调函数;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
.....
.....
// 如果程序唤醒了
if (savedInstanceState != null) {
restoreFragments();
mFragmentCurrentTag = savedInstanceState.getString(KEY_FRAGMENT_TAG);
mIsSaveInstanceCalled = true;
}
Activity的数据加载
一般在onCreate中,加载Activity的数据,其他回调方法很可能被调用,比如:如在onStart中加载了数据,按home,马上又回到页面时,onStart会执行;
示例代码:
/**
* 自动记录 滚动文字
*/
ListView listView;
/**
* 记录内容
*/
EditText et;
// 在这里初始化数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_state1);
listView = (ListView) findViewById(R.id.list);
et = (EditText) findViewById(R.id.et_test);
Log.e(TAG, "onCreate: " + savedInstanceState);
String[] a = new String[255 - 64 + 1];
for (int i = 64; i < 255; i++) {
a[i-64] = ">>>>>" + ((char) i);
}
listView.setAdapter(new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, a));
}
@Override
protected void onStart() {
super.onStart();
}
Fragment的状态保存和恢复
相对于Activity,Fragment的情况,就显得特别复杂,如果有嵌套Fragment,则更复杂了。如果页面不是特别复杂,能不用嵌套fragment,则不用;
** Fragment启动时的生命周期回调:**
** 按home键时:**
之所以执行 onSaveInstanceXXX是因为Activity执行了这个方法;
旋转屏幕时(view的状态自己维护了):
Fragment add 与 remove
remove()是移除fragment, 如果fragment不加入到back stack, remove()的时候, fragment的生命周期会一直走到onDetach().类似于 按 back,activity 正常结束一样;
添加到 backStack就不一样了;remove(), fragment 的生命会走到 onDestroyView(),不会执行onDetach(),此时 fragment本身的实例是存在的,成员变量也存在,但是view销毁了;不要把Fragment的实例状态和View状态混在一起处理,这点非常重要;
当Fragment从back stack中返回, 实际上是经历了一次View的销毁和重建, 但是它本身并没有被重建.
即View状态需要重建, 实例状态不需要重建.
当Fragment被另一个Fragment replace(), 并且压入back stack中, 此时它的View是被销毁的, 但是它本身并没有被销毁.
也即, 它走到了onDestroyView(), 却没有走onDestroy()和onDetact().
等back回来的时候, 它的view会被重建, 重新从onCreateView()开始走生命周期.
在这整个过程中, 该Fragment中的成员变量是保持不变的, 只有View会被重新创建.
在这个过程中, instance state的saving并没有发生.
我们来看看:
// 添加FragmentB
findViewById(R.id.addFragmentB).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment f = fragmentManager.findFragmentByTag(FragmentB.class.getName());
if (f == null) {
f = Fragment.instantiate(getApplicationContext(), FragmentB.class.getName());
}
// 如果不添加返回栈,remove() 该fragment实例会销毁的
fragmentManager.beginTransaction().add(R.id.container, f, FragmentB.class.getName())
.addToBackStack(null).commit();
}
});
// 移除FragmentB
findViewById(R.id.removeFragmentB).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment f = fragmentManager.findFragmentByTag(FragmentB.class.getName());
if (f != null) {
fragmentManager.beginTransaction().remove(f).commit();
}
}
});
上面的代码,我们第一段添加FragmentB,第二段,移除FragmentB,我们打印一下生命周期方法:
可以看到Fragment并没有回到onDetach,onDestroy,也即:fragment 是其对应的View消耗了。但是Fragment的示例还是存在的;
下面显示再次add FragmentB,打印如下:
这个时候我们看看成员变量吧:
如果FragmentB不添加返回键,调用remove(),就类似按返回键一样了,会直接消耗FragmentB,这个机制跟Activity是一致的;
Fragment onCreateView多次执行
了解了上面之后,也就明白为什么 onCreateView会多次执行了吧。
常见的做法,是记录 一个 rootView来记录一下 onCreateView中返回的view,下一次Fragment onCreateView回调时,判断rootView是否为null,来进行是否加载数据,等其他操作;
如下代码:
private View rootView;//缓存Fragment view
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(rootView==null){
rootView=inflater.inflate(R.layout.tab_fragment, null);
}
//缓存的rootView需要判断是否已经被加过parent,
// 如果有parent需要从parent删除,要不然会发生这个rootview已经有parent的错误。
ViewGroup parent = (ViewGroup) rootView.getParent();
if (parent != null) {
parent.removeView(rootView);
}
return rootView;
}
这样可以解决问题,特别是界面layout元素特别多的时候,这样可以看上去可以避免 inflate多次执行一样,的确可以这样,但就是看着特别别扭
但google这样设计,或许是有其他考虑的吧,这里不是很明白;
;如果界面真的切换频率过高,可以考虑使用 hide与show来操作了,来避免上面的代码;
如果不考虑上面的实现方式,我们完全可以不加判断来做,让其直接 inflate吧,但是,这里 界面上view元素的 状态是如何恢复的呢?也没看到,调用onSaveInstacneXX等之类的方法,还是从大神博客中,找到了;
Fragment状态保存入口:####
摘自:http://www.cnblogs.com/mengdd/p/5582244.html
3个入口:
- Activity的状态保存, 在Activity的onSaveInstanceState()里, 调用了FragmentManger的saveAllState()方法, 其中会对mActive中各个Fragment的实例状态和View状态分别进行保存.
- FragmentManager还提供了public方法: saveFragmentInstanceState(), 可以对单个Fragment进行状态保存, 这是提供给我们用的, 其中调用的saveFragmentBasicState()方法即为情况一中所用, 图中已画出标记.
- FragmentManager的moveToState()方法中, 当状态回退到ACTIVITY_CREATED, 会调用saveFragmentViewState()方法, 保存View的状态.
Fragment状态恢复入口:####
三个恢复的入口和三个保存的入口刚好对应.
- 在Activity重新创建的时候, 恢复所有的Fragment状态.
- 如果调用了FragmentManager的方法: saveFragmentInstanceState(), 返回值得到的状态可以用Fragment的setInitialSavedState()方法设置给新的Fragment实例, 作为初始状态.
- FragmentManager的moveToState()方法中, 当状态正向创建到CREATED时, Fragment自己会恢复View的状态.
这三个入口分别对应的情况是:
- 入口1对应系统销毁和重建新实例.
- 入口2对应用户自定义销毁和创建新Fragment实例的状态传递.
- 入口3对应同一Fragment实例自身的View状态重建.
Fragment状态保存恢复和Activity关联:
对应入口1的情况,类似于Activity状态保存于恢复处理;比较好理解,不进行分析了;
Fragment同一实例的View状态恢复
对应入口3的情况,也即:activity是resume状态下,切换fragment,是如何保存自己的状态的?
Fragment被add过,当remove()此fragment时,发现 view 的 onSaveInstanceState会被调用,调用栈如下:
因为 commit不是立刻执行,所以跟踪的堆栈,commit那部分调用丢失了,在这里,可以看到 moveToState, saveFragmentViewState调用了,这也就说明了,remove时,fragment其内部的view会保存状态;
来看看立即执行 commit的调用栈:
不同Fragment实例间的状态保存和恢复
入口1与入口3都是自动处理,入口2需要用户手动来处理;
如果需要在不同fragment实例间传递状态,就需要用到入口2了,手动调用
FragmentManager 的 saveFragmentInstanceState 方法:
public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f);
// 具体实现为:
@Override
public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) {
if (fragment.mIndex < 0) {
throwException( new IllegalStateException("Fragment " + fragment
+ " is not currently in the FragmentManager"));
}
if (fragment.mState > Fragment.INITIALIZING) {
Bundle result = saveFragmentBasicState(fragment);
return result != null ? new Fragment.SavedState(result) : null;
}
return null;
}
恢复时,调用Fragment setInitialSavedState 来实现;
/**
* Set the initial saved state that this Fragment should restore itself
* from when first being constructed, as returned by
* {@link FragmentManager#saveFragmentInstanceState(Fragment)
* FragmentManager.saveFragmentInstanceState}.
*
* @param state The state the fragment should be restored from.
*/
public void setInitialSavedState(SavedState state) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mSavedFragmentState = state != null && state.mState != null
? state.mState : null;
}
注意: 只能在Fragment被加入之前设置(add, replace).
利用这两个方法可以更加自由地保存和恢复状态, 而不依赖于Activity.
这样处理以后, 不必保存Fragment的引用, 每次切换的时候虽然都new了新的实例, 但是旧的实例的状态可以设置给新实例.
详细例子请参考:
http://www.cnblogs.com/mengdd/p/5582244.html
final String STATE_ = "state_";
// 存储fragment状态
SparseArray<Fragment.SavedState> savedStateSparseArray = new SparseArray<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_state_restore_demo);
// 恢复
if (savedInstanceState != null) {
savedStateSparseArray = savedInstanceState.getSparseParcelableArray(STATE_);
}
// 切换
findViewById(R.id.tab1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// save current tab
Fragment tab2Fragment = getSupportFragmentManager().findFragmentByTag(FragmentF.class.getName());
if (tab2Fragment != null) {
// 保存 tab2Fragment的状态
saveFragmentState(1, tab2Fragment);
}
// restore last state, 每次都new
FragmentE tab1Fragment = new FragmentE();
restoreFragmentState(0, tab1Fragment);
// show new tab
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_container, tab1Fragment, FragmentE.class.getName())
.commit();
}
});
findViewById(R.id.tab2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Fragment tab1Fragment = getSupportFragmentManager().findFragmentByTag(FragmentE.class.getName());
if (tab1Fragment != null) {
saveFragmentState(0, tab1Fragment);
}
// 每次都new
FragmentF tab2Fragment = new FragmentF();
restoreFragmentState(1, tab2Fragment);
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_container, tab2Fragment, FragmentF.class.getName())
.commit();
}
});
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSparseParcelableArray(STATE_, savedStateSparseArray);
}
/**
* 手动存状态
*
* @param index
* @param fragment
*/
private void saveFragmentState(int index, Fragment fragment) {
Fragment.SavedState savedState = getSupportFragmentManager().saveFragmentInstanceState(fragment);
savedStateSparseArray.put(index, savedState);
}
/**
* 手动调用 恢复状态
*
* @param index
* @param fragment
*/
private void restoreFragmentState(int index, Fragment fragment) {
Fragment.SavedState savedState = savedStateSparseArray.get(index);
fragment.setInitialSavedState(savedState);
}