即使在设置新适配器后,ViewPager 仍保留片段的实例
ViewPager holds fragment's instance even after setting a new adapter
我在 ViewPager
和 Fragment
的实例中遇到了一些问题。
我有一个 ViewPager
(让我们称其为父亲),其中有 4 个片段,在最后一个片段中,我有另一个 ViewPager
(并称其为子片段),其片段数量是动态的。我的意思是我根据内存中的对象列表创建子对象。因此,如果列表包含 3 个对象,则 Child 将在其中包含 3 个片段。如果在确定的时刻发生了某些事情并且我得到了一个包含 1 个对象的列表,那么我必须只用 1 个片段更新 Child。这里很重要的一点是每个 Child'sFragment
从返回的列表中都有自己的对象,并且是基于这个对象创建的。
我将片段列表设置到子 ViewPager
中的代码如下:
@Override
public void setViewPagerChildFragments(List<Fragment> fragments) {
if (fragments != null) {
DefaultStateViewPagerAdapter adapter = (DefaultStateViewPagerAdapter) mViewpagerChild.getAdapter();
if (adapter == null) {
/* In this case I use getChildFragmentManager() because
it's inside the last fragment of the ViewPager Father*/
adapter = new DefaultStateViewPagerAdapter(getChildFragmentManager(), fragments);
mViewpagerChild.setAdapter(adapter);
} else {
adapter.setFragments(fragments); // Into this method I do notifyDataSetChanged() already
}
}
}
请注意,我尝试使用相同的适配器实例来设置片段,然后通知更改(notifyDataSetChanged()
在方法内部)。如果我没有得到适配器的实例,我会创建一个新实例并将其设置为 ViewPager
子项。
问题发生了,例如,当我用 2 个片段设置 ViewPager
子级时,过了一会儿我需要用 1 个片段设置它。 ViewPager
仅显示其中的 1 个片段,但第二个片段仍处于连接状态且未被破坏。我知道它是因为我做了一个测试调用 getChildFragmentManager().getFragments()
,我可以看到应该被销毁的片段仍然存在。
你可能会说这实际上不是问题,因为 Garbage Collector
可以删除未使用的 Fragment
。但是,例如,如果在某个时刻,我尝试再次将 2 个新片段设置到 ViewPager
子对象中,它会使用未使用的 Fragment
实例而不是创建一个新的实例,不幸的是它也使用相同的对象,而不是正确的新版本。
这是我的DefaultStateViewPagerAdapter
代码:
public class DefaultStateViewPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<Fragment> mFragments;
private ArrayList<String> mFragmentTitles;
public DefaultStateViewPagerAdapter(FragmentManager fm) {
super(fm);
this.mFragments = new ArrayList<>();
this.mFragmentTitles = new ArrayList<>();
}
public DefaultStateViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.mFragments = (ArrayList<Fragment>) fragments;
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public int getItemPosition(Object object) {
int index = mFragments.indexOf(object);
if (index < 0) {
index = POSITION_NONE;
}
return index;
}
@Override
public int getCount() {
return mFragments.size();
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitles.get(position);
}
public void clearFragments() {
mFragments.clear();
mFragmentTitles.clear();
}
public void setFragments(List<Fragment> fragments) {
mFragments = (ArrayList<Fragment>) fragments;
notifyDataSetChanged();
}
public void addFragment(Fragment fragment, String title) {
mFragments.add(fragment);
mFragmentTitles.add(title);
}
}
我已经尝试覆盖 saveState
以避免 ViewPager
使用旧片段的实例,例如:
@Override
public Parcelable saveState() {
return null;
}
成功了,ViewPager
不再使用旧的引用,但未使用的 Fragment
仍然附加,这会导致内存泄漏。
我不知道为什么 ViewPager
即使我设置了一个新的适配器也没有销毁它的碎片。有人遇到过这个问题吗?
我找到了解决方案。
解决方法很简单。我只需要手动将 null
设置为 Child 的适配器。有了这个,ViewPager
被迫销毁每个片段。
所以我在 Fragment
的父亲的 onDestroyView
中添加了:
@Override
public void onDestroyView() {
super.onDestroyView();
mViewpagerChild.removeOnPageChangeListener(mOnPaymentMethodsPageChangeListener);
mViewpagerChild.setAdapter(null); // <-- This is what I added
}
我在 ViewPager
和 Fragment
的实例中遇到了一些问题。
我有一个 ViewPager
(让我们称其为父亲),其中有 4 个片段,在最后一个片段中,我有另一个 ViewPager
(并称其为子片段),其片段数量是动态的。我的意思是我根据内存中的对象列表创建子对象。因此,如果列表包含 3 个对象,则 Child 将在其中包含 3 个片段。如果在确定的时刻发生了某些事情并且我得到了一个包含 1 个对象的列表,那么我必须只用 1 个片段更新 Child。这里很重要的一点是每个 Child'sFragment
从返回的列表中都有自己的对象,并且是基于这个对象创建的。
我将片段列表设置到子 ViewPager
中的代码如下:
@Override
public void setViewPagerChildFragments(List<Fragment> fragments) {
if (fragments != null) {
DefaultStateViewPagerAdapter adapter = (DefaultStateViewPagerAdapter) mViewpagerChild.getAdapter();
if (adapter == null) {
/* In this case I use getChildFragmentManager() because
it's inside the last fragment of the ViewPager Father*/
adapter = new DefaultStateViewPagerAdapter(getChildFragmentManager(), fragments);
mViewpagerChild.setAdapter(adapter);
} else {
adapter.setFragments(fragments); // Into this method I do notifyDataSetChanged() already
}
}
}
请注意,我尝试使用相同的适配器实例来设置片段,然后通知更改(notifyDataSetChanged()
在方法内部)。如果我没有得到适配器的实例,我会创建一个新实例并将其设置为 ViewPager
子项。
问题发生了,例如,当我用 2 个片段设置 ViewPager
子级时,过了一会儿我需要用 1 个片段设置它。 ViewPager
仅显示其中的 1 个片段,但第二个片段仍处于连接状态且未被破坏。我知道它是因为我做了一个测试调用 getChildFragmentManager().getFragments()
,我可以看到应该被销毁的片段仍然存在。
你可能会说这实际上不是问题,因为 Garbage Collector
可以删除未使用的 Fragment
。但是,例如,如果在某个时刻,我尝试再次将 2 个新片段设置到 ViewPager
子对象中,它会使用未使用的 Fragment
实例而不是创建一个新的实例,不幸的是它也使用相同的对象,而不是正确的新版本。
这是我的DefaultStateViewPagerAdapter
代码:
public class DefaultStateViewPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<Fragment> mFragments;
private ArrayList<String> mFragmentTitles;
public DefaultStateViewPagerAdapter(FragmentManager fm) {
super(fm);
this.mFragments = new ArrayList<>();
this.mFragmentTitles = new ArrayList<>();
}
public DefaultStateViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.mFragments = (ArrayList<Fragment>) fragments;
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public int getItemPosition(Object object) {
int index = mFragments.indexOf(object);
if (index < 0) {
index = POSITION_NONE;
}
return index;
}
@Override
public int getCount() {
return mFragments.size();
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitles.get(position);
}
public void clearFragments() {
mFragments.clear();
mFragmentTitles.clear();
}
public void setFragments(List<Fragment> fragments) {
mFragments = (ArrayList<Fragment>) fragments;
notifyDataSetChanged();
}
public void addFragment(Fragment fragment, String title) {
mFragments.add(fragment);
mFragmentTitles.add(title);
}
}
我已经尝试覆盖 saveState
以避免 ViewPager
使用旧片段的实例,例如:
@Override
public Parcelable saveState() {
return null;
}
成功了,ViewPager
不再使用旧的引用,但未使用的 Fragment
仍然附加,这会导致内存泄漏。
我不知道为什么 ViewPager
即使我设置了一个新的适配器也没有销毁它的碎片。有人遇到过这个问题吗?
我找到了解决方案。
解决方法很简单。我只需要手动将 null
设置为 Child 的适配器。有了这个,ViewPager
被迫销毁每个片段。
所以我在 Fragment
的父亲的 onDestroyView
中添加了:
@Override
public void onDestroyView() {
super.onDestroyView();
mViewpagerChild.removeOnPageChangeListener(mOnPaymentMethodsPageChangeListener);
mViewpagerChild.setAdapter(null); // <-- This is what I added
}