前几天遇到一个bug,是activity被回收后在恢复时一个ViewPager中的fragment状态错误引起的 ,花了些时间解决,记录一下:
现象
Activity A中有fragment B, B中有个ViewPager V,V的adapter设置的是FragmentPagerAdapter,也就是说fragment B有几个childFragment. 在手机内存紧张时A被回收,但在A恢复时,B中的ViewPager V没有显示,而且页面下拉刷新应用crash(下拉刷新时用到了V的adapter的实例)
原因
通过查看源码,发现Fragment会在被回收时保存ChildFragmentManager的状态:如下
//fragmentactivity
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable p = mFragments.saveAllState(); //最终调用mFragmentManager.saveAllState()
...
...
}
//FragmentManager
Parcelable saveAllState() {
...
if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
fs.mSavedFragmentState = saveFragmentBasicState(f); //会调用到fragment的performSaveInstanceState
...
}
//fragment
void performSaveInstanceState(Bundle outState) {
onSaveInstanceState(outState);
if (mChildFragmentManager != null) {
Parcelable p = mChildFragmentManager.saveAllState();
if (p != null) {
outState.putParcelable(FragmentActivity.FRAGMENTS_TAG, p);
}
}
}
可以看到fragment的childfragmentmanager也会被记录下来,也就是说ChildFragment会被记录下来,恢复的时候也会恢复出来.
再来看看viewpager的状态保存相关代码:
//viewpager
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.position = mCurItem;
if (mAdapter != null) {
ss.adapterState = mAdapter.saveState();
}
return ss;
}
//PagerAdapter
public Parcelable saveState() {
return null;
}
可见pageradapter并不会保存状态,而子类fragmentpageradapter默认也没有保存状态
所以当activity A恢复状态时,fragment B恢复了,viewpager中的几个fragment也恢复了,但是 viewpager的adapter却没有恢复,于是按我之前的逻辑就会重新创建一个fragmentpageradapter,而这时候我的做法是重新创建了几个fragment的实例传给了fragmentpageradapter,再来看看fragmentpageradapter显示fragment的相关代码
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position); //1
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
可以发现由于Fragment B已经通过恢复childFragmentManager恢复了viewpager中的几个fragment,所以上面的代码不会走到1处,也就是说显示的fragment是从回收状态恢复时childfragmentmanager创建的,而不是我们自己创建的, 所以问题就是这个.
那怎么解决这个问题呢?
我的办法是在创建fragmentpageradapter时判断Fragment B有没有viewpager中的几个fragment实例,如果有就将其传给fragmentpageradapter,否则再创建fragment的实例:
List<Fragment> fragments = getChildFragmentManager().getFragments();
ArrayList<HomeListFragment> list = new ArrayList<>();
if (fragments == null || fragments.isEmpty()) {
list.add(HomeListFragment.newInstance("1"));
list.add(HomeListFragment.newInstance("2"));
list.add(HomeListFragment.newInstance("3"));
list.add(HomeListFragment.newInstance("4"));
} else {
for (Fragment fragment : fragments) {
if (fragment instanceof HomeListFragment) {
list.add((HomeListFragment) fragment);
}
}
}
childFragmentManager在保存状态和恢复状态时会保留顺序,所以不用担心恢复后的顺序和viewpager中的顺序不对。
总结
其实说了这么多,关键就是一条:fragment的获取一定要通过fragmentmanager .可以用findFragmentWithTag或者getFragments来得到特定或者所有fragment。如果没有获取到fragment实例再去创建。如果通过是ViewPager来间接控制Fragment更要注意,传给FragmentPagerAdapter的Fragment实例一定要和FragmentManager中的一致,如果FragmentManager中没有再去创建.