1. 程式人生 > >解決ViewPager展示Fragment時重新設定setAdapter不會重置Fragment的bug

解決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,觀察原始碼便知真相在這裡插入圖片描述
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,至此大功告成.