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()
}
外层嵌套参考最上面那位大佬的方案即可。
至于原因,那位大佬也给出来了。