即使在设置新适配器后,ViewPager 仍保留片段的实例

ViewPager holds fragment's instance even after setting a new adapter

我在 ViewPagerFragment 的实例中遇到了一些问题。

我有一个 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
}