viewpager嵌套Fragment被系统回收

viewpager嵌套Fragment被系统回收

  • 先看下activity层的处理方案
  • 我的布局层次介绍
  • 遇到的问题
  • 问题分析跟进

先看下activity层的处理方案

fragmentPagerAdapter源码
在对fragment绑定的时候,会先findFragmentByTag,如果之前有回收的fragment,会进行复用,也就是说不会执行新生成的fragment.attach(),相当于新的fragment是没有用的,但是我们的成员变量指向的确是这个新生成的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);
        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;
}

解决:在onRestoreInstance当中对旧的fragment重新引用

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    Fragment fragmentFirst = getSupportFragmentManager().findFragmentByTag(makeFragmentName(mViewPager.getId(), 0));
    if (fragmentFirst != null) {
       mFragmentA = (FragmentA) fragmentFirst;
    }
    Fragment fragmentSecond = getSupportFragmentManager().findFragmentByTag(makeFragmentName(mViewPager.getId(), 1));
    if (fragmentSecond != null) {
        mFragmentB=  (FragmentB)fragmentSecond;
    }
}

/**
 * 参考fragmentPagerAdapter中的寻找fragment的方法
 */
private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}

这是我在网上找到的外层Activity层解决方案

我的布局层次介绍

activity里面有Viewpager控件,里面有5个Fragment,可以左右滑。
目前就有一层,Viewpager加Fragment
Fragment里面又有Viewpager,Viewpager里面又2个Fragment
第二层,Viewpager加Fragment。

遇到的问题

我写了个需求任务,在内层的Viewpager中的Fragment中写了个recyclerView,要求刷新频率比较高,而且我的View是根据全局缓存的数据集做显示隐藏判断。
1、在手机放置几个小时之后被系统回收,出现FC空指针,由于我已经下班,我的同事直接对View进行null情况下的判断,解决空指针,但是时间紧没有找到原因。
2、没有空指针的情况下放置几个小时之后被系统回收会出现RV隐藏,其它控件都是正常显示,却能点击响应。
3、在后面又加入了新的任务,对控制进行延迟初始化:lateinit var
出现FC,提示控件未初始化,暂时改成 ? = null方式避免了空指针崩溃。

问题分析跟进

首先很明显,手机放置久了APP被回收之后重新进来导致的。
可以使用横竖屏切换来模拟或者设置手机不保留活动来模拟该情况。

在公司测试人员的帮助下,对FC的代码进行判断暂时避免FC,进行分析为什么RV不见了。
debug结果:重新初始化控件是null,之前同事帮我加的判断进入之后return了。

为什么其它控件能够显示,它们就没有问题吗?
在debug的帮助下,我看到了所有的控件都是null,我同事的代码在控件使用前加了?(kontlin),所以没崩溃,因为进入了判断ruturn之前会隐藏掉控件,所以rv没有显示。其他控件能正常显示,而且控件的点击是正常响应的,但是无法刷新,因为代码中Fragment不为空,但是Fragment里面的控件是null,由于kotlin的?模式,导致没有崩溃,代码也没有执行,如果不仔细看真不知道是没有刷新。

使用横竖屏切换来模拟查看是否有重新初始化
结果:Fragment的生命周期全部都走了了,但是结果是:
Fragment不为空,打印它的内存地址也是新初始化的Fragment,地址一样就是这个fragment。但是debug查看它的属性发现它里面的控件全是null。

这个结果就像是:new Fragment(),生命全部都走了,对象不为空,但是对象里面的属性全都是null。点击能响应,肯定是旧的fragment在响应,但是我们拿到的却是新的Fragment。

暂时好像没啥思路,上网一顿查找,有人说出现了两个一样的Fragment重叠等,还有其它一堆不怎么相关的说onsaveInstancestate的等,都没用。

那个重叠Fragment的说法,让我去检查了FragmentManager,我发现getFragments()确实能看到里面存着旧的Fragment,在debug的情况下发现:
在Fragment生命周期走之前,FragmentManager里面存着的Fragment里面的控件也是空的,但是在走完生命周期流程之后发现它里面的控件不为空了。

这里有点奇怪,既然新Fragment生命周期都走了为何属性还是为空,旧的fragment,按照Fragment源码的大致思路是建立集合View树,缓存了相关的数据,然后重新再拿一遍,印象中记得好像成员变量不缓存,但是实际上是成员变量还是有数据的。
这里我没有再深入进入,有可能走的是旧Fragment的生命周期,所以新Fragment里面的变量全是null,有兴趣的小伙伴可以去研究一下,这个问题是必现的。

按照那位说Fragment重叠说法的仁兄,先把FragmentManager里面的Fragment移除掉,结果跟之前没有区别,这种做法不可取,还是跟原来没区别。新Fragment里面的属性全是null。
那么我就反过来,既然生命周期走完FragmentManager里面的Fragment是好的,可以用的,我就把里面的Fragment拿出来用即可。

内层的嵌套是在Framgent上是没有onRestoreInstance,所以直接在onResume中替换:

try {
            var listFragment = childFragmentManager.fragments
            if (listFragment != null && listFragment.size > 0) {
                when(listFragment.size) {
                    1 -> homeRecommendFragment = listFragment[0] as HomeRecommendFragment
                    2 -> {
                        homeRecommendFragment = listFragment[0] as HomeRecommendFragment
                        newPeopleFragment = listFragment[1] as HomeNewPeopleFragment
                    }
                }
            }
        } catch (e : Throwable) {
            e.printStackTrace()
        }

外层嵌套参考最上面那位大佬的方案即可。
至于原因,那位大佬也给出来了。

你可能感兴趣的:(日常,android,java,viewpager)