解决ViewPager展示Fragment时重新设置setAdapter不会重置Fragment的bug

用过ListView和RecycleView的人都知道不管当前列表的浏览记录在哪里,只要重新setAdapter,列表就会重置,即从第一条item开始显示.
因此,想当然的我也就认为ViewPager也是这个样子的.结果并不是我想的那么简单,重复setAdapter并没什么卵用,Fragment的状态还是上一次所看到的样子,并没有重新初始化,Fragment的几什么周期方法一个都没有运行…实在无语…

经调试后发现ViewPager重新setAdapter并没有执行FragmentPagerAdapter的getItem方法,因此Adapter中的Fragment并不会重新初始化.

例如我的FragmentPagerAdapter :

class PageAdapter extends FragmentPagerAdapter {
    public PageAdapter(FragmentManager fm) {
        super(fm);
    }
    @Override
    public Fragment getItem(int position) {
        Category c= mData.get(position % mData.size());
        return NewsFragment.newInstance(c.id);
    }
    @Override
    public int getCount() {
        return mData.size();
    }
}

既然getItem方法没有调用,那么就得看看其父类FragmentPagerAdapter 的源码了,看看getItem方法是在哪里调用的.发现是在父类的instantiateItem方法中调用的,源码如下:

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);//从缓存中查找该fragment
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position); //这里才会执行getItem方法
        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;
}

注意观察Fragment fragment = mFragmentManager.findFragmentByTag(name);这句代码,这里FragmentManager是通过tag别名的方式来从缓存中查找对应的Fragment,其tag的命名是有一套命名规范的,是通过ViewPager的id和getItemId的返回值来生成tag.

读到这里的时候,你应该就可以明白怎么做了,如果想要保证setAdapter方法会执行getItem方法,那么就得保证findFragmentByTag方法返回的Fragment是null的.
如何做到呢?
我这里想到2种解决思路:
1.重写FragmentPagerAdapter 的getItemId方法,每次都返回不同的id,这样就可以保证生成的tag别名每次都是不一样的,因此也无法从缓存中查找到值
2.清空mFragmentManager内的缓存.

清空FragmentManager的缓存

我这里主要介绍的是第2种方法,即清空缓存的方式.
首先mFragmentManager,你得明白这个是什么,这个其实就是你创建FragmentPagerAdapter 的实现类的时候赋值的,一般是通过getSupportFragmentManager或者getChildFragmentManager方式得到的值,前者是在Activity中使用,后者是在Fragment中使用.

public abstract class FragmentPagerAdapter extends PagerAdapter {
    private static final String TAG = "FragmentPagerAdapter";
    private static final boolean DEBUG = false;

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;
    private Fragment mCurrentPrimaryItem = null;

    public FragmentPagerAdapter(FragmentManager fm) {
        mFragmentManager = fm; //这里赋值
    }
    ....
}

FragmentManager 是一个抽象类,其findFragmentByTag也是抽象的方法

public abstract Fragment findFragmentByTag(String tag);

因此只能找他的实现类了,即FragmentManagerImpl
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {..}
查看其findFragmentByTag的具体实现:

@Override
public Fragment findFragmentByTag(String tag) {
    if (tag != null) {
        // First look through added fragments.
        for (int i=mAdded.size()-1; i>=0; i--) { //1
            Fragment f = mAdded.get(i);
            if (f != null && tag.equals(f.mTag)) {
                return f;
            }
        }
    }
    if (mActive != null && tag != null) { //2
        // Now for any known fragment.
        for (int i=mActive.size()-1; i>=0; i--) {
            Fragment f = mActive.valueAt(i);
            if (f != null && tag.equals(f.mTag)) {
                return f;
            }
        }
    }
    return null;
}

注意查看上面标注位置的1和2,分别有2个属性mAdded和mActive。
findFragmentByTag的操作就是操作这2个东东,那么这2个东东到底是啥呢,mAdded其实是一个ArrayList集合,而mActive就是一个SparseArray,观察源码便知真相解决ViewPager展示Fragment时重新设置setAdapter不会重置Fragment的bug_第1张图片
mAdded和mActive都各自维护了已经加载过的Fragment,也就是这里所说的缓存,既然这样,我就可以通过反射拿到这2个东东,然后调用它们的clear方法来清空缓存,那么findFragmentByTag方法拿到的结果就是null了.

下面看具体的逻辑,在setAdapter方法之前执行下面代码:

//先保证ViewPager之前已设置过Adapter,这样才有可能存在缓存
if (mContentVp.getAdapter() != null) {
	//获取FragmentManager实现类的class对象,这里指的就是FragmentManagerImpl
    Class<? extends FragmentManager> aClass = getChildFragmentManager().getClass();
    try {
    	//1.获取其mAdded字段
        Field f = aClass.getDeclaredField("mAdded");
        f.setAccessible(true);
        //强转成ArrayList
        ArrayList<Fragment> list = (ArrayList) f.get(getChildFragmentManager());
        //清空缓存
        list.clear();
        
        //2.获取mActive字段
        f = aClass.getDeclaredField("mActive");
        f.setAccessible(true);
        //强转成SparseArray
        SparseArray<Fragment> array  = (SparseArray) f.get(getChildFragmentManager());
     	//清空缓存
        array.clear();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
//再次设置ViewPager的Adapter
mContentVp.setAdapter(new PageAdapter(getChildFragmentManager()));

ok,至此大功告成.

你可能感兴趣的:(Android)