完美解决Fragment重叠的问题
一 原因
app所分配的内存不足时,非可见Activity都有可能被回收,FragmentActivity回收表现在:当FragmentActivity进入后台有可能被系统回收时,系统会回调Activity的
/**
* Save all appropriate fragment state.
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mPendingFragmentActivityResults.size() > 0) {
outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
}
outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
}
}
生命周期方法。很明显FragmentActivity保存了之前所有的Fragment状态并且序列化(注意:这里只保存了所有fragment的状态,并不是保存当前实例,当前的fragment实例会随着系统的回收而一起回收)
当系统检测到该FragmentActivity需要回收时,再次进入该Activity就会重新实例化FragmentActivity,并带入上次onSaveInstance保存的所有状态,其中就包括所有的Fragment,而以这种情况实例化的FragmentActivity在onCreate的时候会自动处理保存下来的fragments,请看以下FragmentActivity的源码
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
super.onCreate(savedInstanceState);
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mFragments.restoreLoaderNonConfig(nc.loaders);
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
// Check if there are any pending onActivityResult calls to descendent Fragments.
if (savedInstanceState.containsKey(NEXT_CANDIDATE_REQUEST_INDEX_TAG)) {
mNextCandidateRequestIndex =
savedInstanceState.getInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG);
int[] requestCodes = savedInstanceState.getIntArray(ALLOCATED_REQUEST_INDICIES_TAG);
String[] fragmentWhos = savedInstanceState.getStringArray(REQUEST_FRAGMENT_WHO_TAG);
if (requestCodes == null || fragmentWhos == null ||
requestCodes.length != fragmentWhos.length) {
Log.w(TAG, "Invalid requestCode mapping in savedInstanceState.");
} else {
mPendingFragmentActivityResults = new SparseArrayCompat<>(requestCodes.length);
for (int i = 0; i < requestCodes.length; i++) {
mPendingFragmentActivityResults.put(requestCodes[i], fragmentWhos[i]);
}
}
}
}
if (mPendingFragmentActivityResults == null) {
mPendingFragmentActivityResults = new SparseArrayCompat<>();
mNextCandidateRequestIndex = 0;
}
mFragments.dispatchCreate();
}
注意:不同版本的源码大同小异。 但是,一般情况下我们在创建FragmentActivity的时候会手动创建Fragment实例并add到Activity上,那么问题就处在这里,这时候系统自己生成了一套Fragmemts自己又创建了一套Fragments。而我们在代码中只操作了自己创建的那些Fragments,从而导致界面出现重叠的情况。
二 解决问题
既然系统根据之前的fragments状态恢复到新的Activity实例中,那么本着不产生新问题以及不破坏Activity生命周期的原则(有人说注释掉onSaveInstance方法),且看我如何完美解决此问题:
正常创建Activity时,我们将fragments保存起来,以便代码方便使用
private BaseFragment[] fragments;
也许你的是ArrayList,SparseList都可以(对于fragments量小,基本只有索引查找时建议使用数组形式,减少开销)
以下是我如何动态创建fragment以及如何hide和show,其中enum类型的Item你们可以理解成一个int型的index即可,我自己封装了一些业务需要,
public void showFragment(Item item) {
if (item == null || activity.getCurrentItem() == item) {
return;
}
hideAll();
FragmentTransaction transaction = manager.beginTransaction();
if (fragments[item.getIndex()] == null) {
switch (item) {
case HOME://首页
fragments[item.getIndex()] = new HomeFragment();
break;
case USER_CENTER://用户中心
fragments[item.getIndex()] = new UserCenterFragment();
break;
default:
break;
}
transaction.add(R.id.container, fragments[item.getIndex()], String.valueOf(item.getIndex()));
} else {
transaction.show(fragments[item.getIndex()]);
}
transaction.commitAllowingStateLoss();
}
这是正常情况下的创建,那么针对系统回收的情况我们该怎么处理呢?简单!!!!!! 我们只需要将系统创建的Fragments存储到fragments数组中即可,当代码走到showFragment()方法的时候,就不会再次创建Fragment实例了。
直接上代码:(请注意,这个方法是我自己写的,不是系统方法)最终这个方法应该在Activity的onCreate方法中调用
public void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
Item currentItem = null;
int index = savedInstanceState.getInt(MainActivity.ITEM_INDEX_KEY, Item.HOME.getIndex());
for (int i = 0; i < fragments.length; i++) {
int tmpIndex = Item.values()[i].getIndex();
fragments[i] = (BaseFragment) manager.findFragmentByTag(String.valueOf(tmpIndex));
if (index == tmpIndex) {
currentItem = Item.values()[i];
}
}
if (currentItem != null) {
activity.setCurrentItem(currentItem);
} else {
activity.setCurrentItem(Item.HOME);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
//注意此代码必须在super.onCreate之后,因为必须保证父类FragmentActivity的onCreate优先执行,
if (savedInstanceState != null) {
containerModel.onRestoreInstanceState(savedInstanceState);
} else {
navigationView.setCurrentItem(Item.HOME);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
//注意此代码必须在super.onCreate之后,因为必须保证父类FragmentActivity的onCreate优先执行,
if (savedInstanceState != null) {
containerModel.onRestoreInstanceState(savedInstanceState);
} else {
navigationView.setCurrentItem(Item.HOME);
}
}
好了 问题解决!!
附:我在onRestoreInstanceState中保存了一个当前Fragment的索引,也是为了解决,当重新进来时每次都展示第一个fragment的问题,这样用户就察觉不到后台系统回收每次都回到首页的问题。索引的保存请看:
@Override
protected void onSaveInstanceState(Bundle outState) {
Item item = getCurrentItem();
if (item != null) {
outState.putInt(ITEM_INDEX_KEY, item.getIndex());
}
super.onSaveInstanceState(outState);
}