1. 问题现象
在之前开发的一款应用中,有用户反映将App长期置于后台(20-30min,小米Mix2)后返回,在首页切换Fragment,有时会出现Fragment重叠显示的问题,示意图如下:
正常情况:
异常情况:
离开时用户停留在第三个Fragment,返回时TabLayout选择的是第一个Fragment,第一个和第三Fragment同时显示,并且此时切换Fragment,第三个Fragment并不会隐藏。
显示和隐藏Fragment的代码如下:
private void showFragmentByPosition(int position) {
hideAllFragment();
switch (position) {
case INDEX_ONE:
showOneFragment();
break;
case INDEX_TWO:
showTwoFragment();
break;
case INDEX_THREE:
showThreeFragment();
break;
default:
break;
}
}
private void hideAllFragment() {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (oneFragment != null) {
transaction.hide(oneFragment);
}
if (twoFragment != null) {
transaction.hide(twoFragment);
}
if (threeFragment != null) {
transaction.hide(threeFragment);
}
transaction.commitAllowingStateLoss();
}
private void showOneFragment() {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (oneFragment == null) {
oneFragment = OverlapOneFragment.newInstance();
transaction.add(R.id.fl_container, oneFragment);
} else {
transaction.show(oneFragment);
}
transaction.commitAllowingStateLoss();
}
2. 问题分析及复现
考虑到APP正常使用时不会出现该问题,而长期置于后台后返回可能出现该问题,怀疑是因为APP长期处于后台,首页Activity被系统销毁,而返回APP时,重建首页Activity,Fragment也在此时被重建,引起Fragment显示错乱。
在隐藏和显示Fragment处添加打印验证问题:
private void showOneFragment() {
...
if (oneFragment == null) {
Logger.e("oneFragment为null,新建");
...
} else {
transaction.show(oneFragment);
}
}
private void hideAllFragment() {
...
if (threeFragment != null) {
transaction.hide(threeFragment);
} else {
Logger.e("threeFragment 为null,无需隐藏");
}
...
}
为便于验证该问题,打开“开发者选项”->“不保留活动”功能,模拟Activity被系统销毁的情形,运行程序,首页Activity销毁重建时的打印如下:
打印日志验证了我们的猜想,即因为APP长期处于后台,首页Activity被系统销毁,而返回APP时,重建首页Activity,此时默认显示第一个Fragment,这就是为什么退出时停留在第三个Fragment,而返回时显示第一个Fragment;
重建Activity时,Fragment也会被重建,并保留之前的视图状态(隐藏第一第二个Fragment,显示第三个Fragment),重建后变量threeFragment为null,并不会在调用hideAllFragment()方法时隐藏第三个Fragment,因此返回App时会显示第三个Fragment,又由于调用showThreeFragment()方法时会新建一个ThreeFragment并赋值给变量threeFragment,导致此时存在两个ThreeFragment实例,因此切换选项卡时,新建的ThreeFragment会隐藏,系统恢复的ThreeFragment不会隐藏,在视觉上产生ThreeFragment没有隐藏的现象。
3. 解决方法
既然系统会恢复Fragment,那么我们就尽可能的复用之前的Fragment,在Activity中有一个onAttachFragment(Fragment fragment)方法,会在一个Fragment附着到Activity上时调用,利用该特性,添加如下代码:
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
Logger.e("fragment is " + fragment.toString());
if (oneFragment == null && fragment instanceof OverlapOneFragment) {
Logger.e("onAttachFragment 赋值oneFragment");
oneFragment = (OverlapOneFragment) fragment;
} else if (twoFragment == null && fragment instanceof OverlapTwoFragment) {
Logger.e("onAttachFragment 赋值twoFragment");
twoFragment = (OverlapTwoFragment) fragment;
} else if (threeFragment == null && fragment instanceof OverlapThreeFragment) {
Logger.e("onAttachFragment 赋值threeFragment");
threeFragment = (OverlapThreeFragment) fragment;
}
}
运行程序,打印日志如下:
此时不再出现Fragment重叠显示的问题,但用户离开时停留在第三个Fragment,现在返回时显示的是第一个Fragment,还需进一步优化。
利用Activity异常情况的生命周期,在onSaveInstanceState()方法中保存用户当前停留位置。
修改代码如下:
@Override
protected void onSaveInstanceState(Bundle outState) {
int curTabPosition = tabLayout.getSelectedTabPosition();
outState.putInt(CURRENT_TAB_POSITION, curTabPosition);
super.onSaveInstanceState(outState);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState != null) {
curPosition = savedInstanceState.getInt(CURRENT_TAB_POSITION, INDEX_ONE);
}
initView();
}
private void initView() {
...
tabLayout.getTabAt(curPosition).select();
}
经验证,这样修改代码后已解决Fragment重叠显示的问题