ViewPager源码解析之FragmentPagerAdapter和FragmentStatePagerAdapter

来源:互联网 发布:putty上传文件到linux 编辑:程序博客网 时间:2024/05/18 02:53

从FragmentPagerAdapter说起

我们经常使用的是ViewPager和Fragment的配合。

在第一篇中我们提到了如果ViewPager使用了FragmentPagerAdapter,ViewPager添加View的时机是在onMeasure()的populate()中。

//ViewPager.java@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        ...        // Make sure we have created all fragments that we need to have shown.        populate();        ...    }

populate()会调用到addNewItem()来初始化所有需要缓存的页面信息。用到了ViewPagerAdapter的instantiateItem()。

    //ViewPager.java        ItemInfo addNewItem(int position, int index) {        ItemInfo ii = new ItemInfo();        ii.position = position;        ii.object = mAdapter.instantiateItem(this, position);        ii.widthFactor = mAdapter.getPageWidth(position);        if (index < 0 || index >= mItems.size()) {            mItems.add(ii);        } else {            mItems.add(index, ii);        }        return ii;    }

以FragmentPagerAdapter为例,我们看下它的instantiateItem()。主要做了这么几件事:

1.开启Transaction

2.获得Fragment并做相应动作(可能是recreate后再FragmentManager中恢复的,也可能是第一次添加)

3.设置MenuVisibility和UserVisibleHint

    //FragmentPagerAdapter.java    @Override    public Object instantiateItem(ViewGroup container, int position) {        //1        if (mCurTransaction == null) {            mCurTransaction = mFragmentManager.beginTransaction();        }        final long itemId = getItemId(position);        //2        String name = makeFragmentName(container.getId(), itemId);        Fragment fragment = mFragmentManager.findFragmentByTag(name);        if (fragment != null) {            ...            mCurTransaction.attach(fragment);        } else {            fragment = getItem(position);           ...            mCurTransaction.add(container.getId(), fragment,                    makeFragmentName(container.getId(), itemId));        }        //3        if (fragment != mCurrentPrimaryItem) {            fragment.setMenuVisibility(false);            fragment.setUserVisibleHint(false);        }        return fragment;    }

之后ViewPager会在populate()中调用finishUpdate()提交事务,然后Fragment就交给了对应的FragmentManager管理,与Activity的生命周期同步。

//ViewPager.javavoid populate(int newCurrentItem) {    ...  mAdapter.finishUpdate(this);    ...}//FragmentPagerAdapter.java    @Override    public void finishUpdate(ViewGroup container) {        if (mCurTransaction != null) {            mCurTransaction.commitAllowingStateLoss();            mCurTransaction = null;            mFragmentManager.executePendingTransactions();        }    }

我们知道在Fragment和Activity生命周期同步的时候,会把自身的View添加到一个Container中,而这个Container就是在ViewPager。

之后对应的FragmentManager管理着所有Fragment的状态,如果ViewPager显示出来了,那么此时所有Fragment的状态是Resumed。也就是说所有缓存的Fragment的状态是和Activity同步的,不管它有没有作为ViewPager的主显示。

而ViewPager的滑动会通过FragmentPagerAdapter的instantiateItem()和destroyItem()造成个别Fragment的生命周期发生改变。

验证

我进行了简单的生命周期的验证,一个ViewPager下设置了4个Fragment(编号为1234),配合ViewPagerAdapter,默认缓存页面信息是1个,开始时页面在第1个。下面直接把Log信息贴上来。

图1是初始化的时候,由于缓存了1个页面信息,所以1和2会走相应的生命周期。(这里实在没有想清楚1和2的生命周期会交替进行,希望懂的朋友指点一下)

图1
图1

图2是从1滑动到2,此时3开始初始化。
这里写图片描述
图2

图3是2滑动到3,此时1超出缓存范围,舍弃,4进入缓存范围,初始化。
这里写图片描述
图3

图4是由3滑动到2,此时1进入缓存范围,更新到最新状态,4超出缓存范围,舍弃。
这里写图片描述
图4

instantiateItem()

让我们对应上面的滑动来看一下。populate()函数中会缓存一定数量的页面,范围是左边界到右边界。ItemInfo.Object要用这个函数得到。上面说过这个函数主要做了几件事,现在来说下主要的部分。分支2是第一次会进入的地方。FragmentTransaction添加了一个add事务,参数包括了一个Tag。

    @Override    public Object instantiateItem(ViewGroup container, int position) {        ...        String name = makeFragmentName(container.getId(), itemId);        Fragment fragment = mFragmentManager.findFragmentByTag(name);      //分支1        if (fragment != null) {           ...            mCurTransaction.attach(fragment);        } else {//分支2            fragment = getItem(position);          ...            mCurTransaction.add(container.getId(), fragment,                    makeFragmentName(container.getId(), itemId));        }       ...    }

我们知道事务commit()之后最终会调用到FragmentManager的addFragment(),关键的几步是Fragment添加到了FragmentManager的mActive、mAdded中,mAdded=true。随后会move到和Activity相同的的状态。经历的生命周期方法和上面图1一样。

    public void addFragment(Fragment fragment, boolean moveToStateNow) {        ...        //添加进FragmentManager的mActive列表中并为Fragment.mIndex分配唯一值        makeActive(fragment);        if (!fragment.mDetached) {            ...            mAdded.add(fragment);            fragment.mAdded = true;            fragment.mRemoving = false;            if (fragment.mHasMenu && fragment.mMenuVisible) {                mNeedMenuInvalidate = true;            }            ...        }    }

那么分支1什么时候会进入呢,是在Fragment滑出缓存界线后再次进入缓存界线的时候。也就是调用过destroyItem()后再次调用instantiateItem()时,找到了Fragment就进入分支1(等下会说为什么能找到fragment),此时FragmentTransaction添加了一个attach事务。最终会调用到FragmentManager的attachFragment(),这个方法是针对mDetached=true的Fragment的。主要将Fragment添加到FragmentManager的mAdded中并且move到最新状态。这里还要说一下,此时Fragment的状态是从CREATED而非INITIALIZING开始move的(等下会说明为什么状态是CREATED),所以并不会经历onAttach和onCreate生命周期。这里的生命周期和上一节图4中的MyFragment1的生命周期吻合。

public void attachFragment(Fragment fragment, int transition, int transitionStyle) {        if (fragment.mDetached) {            fragment.mDetached = false;            if (!fragment.mAdded) {                mAdded.add(fragment);                fragment.mAdded = true;                if (fragment.mHasMenu && fragment.mMenuVisible) {                    mNeedMenuInvalidate = true;                }                moveToState(fragment, mCurState, transition, transitionStyle, false);            }        }    }

destroyItem()

populate()函数中,越界的页面会被“移除”,除了在ViewPager中的List中会清除对应的ItemInfo,还会调用这个方法。FragmentTransaction添加了一个detach事务。

   //FragmentPagerAdapter.java    @Override    public void destroyItem(ViewGroup container, int position, Object object) {        if (mCurTransaction == null) {            mCurTransaction = mFragmentManager.beginTransaction();        }        if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object                + " v=" + ((Fragment)object).getView());        mCurTransaction.detach((Fragment)object);    }

一系列调用过后,最终会回调到FragmentManager的detachFragment()。这个方法赋值mDetached = true,mAdded = false,并且只在FragmentManager的mAdded移除了该Fragment,也就是说mActive中还是引用了该Fragment(解释了上面可以找到Fragment的原因),最后moveToState传入了Fragment.CREATED作为参数,所以此时并没有走onDestroy和onDetach参数,也会影响到下一次它的“升级状态同步”(解释了上面没有onAttach和onCreate的原因)。也就是说被detach的Fragment只是被单纯地destroy了View。这里的生命周期和图4中MyFragment4的生命周期吻合,也和图3中MyFragment1的生命周期吻合。

//FragmentManagerImpl.javapublic void detachFragment(Fragment fragment, int transition, int transitionStyle) {        if (!fragment.mDetached) {            fragment.mDetached = true;            if (fragment.mAdded) {                // We are not already in back stack, so need to remove the fragment.                if (mAdded != null) {                    if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);                    mAdded.remove(fragment);                }                if (fragment.mHasMenu && fragment.mMenuVisible) {                    mNeedMenuInvalidate = true;                }                fragment.mAdded = false;                moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false);            }        }    }

FragmentStatePagerAdapter又是什么情况

FragmentPagerAdapter利用了attach和detach事务,将Fragment保存在了内存之中,这些Fragment只要被添加到了FragmentManager中,他们就一直存在,只是根据缓存区域的移动而选择是否清除View

那么FragmentStatePagerAdapter又有什么不同呢?

看一下它的这两个成员。

//FragmentStatePagerAdapter.javaprivate ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

Fragment.SavedState是Fragment的一个继承了Parcelable接口的静态内部类,维护了一个Bundle成员。这个对象应该是用来持久化保存数据的。mFragments后面再说。

    //Fragment.java    public static class SavedState implements Parcelable {        final Bundle mState;        SavedState(Bundle state) {            mState = state;        }        SavedState(Parcel in, ClassLoader loader) {            mState = in.readBundle();            if (loader != null && mState != null) {                mState.setClassLoader(loader);            }        }       ...    }

这次我们从destroyItem()看起,当页面移出了缓存区域,这个方法被调用到。下面具体说一下。

    @Override    public void destroyItem(ViewGroup container, int position, Object object) {        Fragment fragment = (Fragment) object;        if (mCurTransaction == null) {            mCurTransaction = mFragmentManager.beginTransaction();        }      //position位置的fragment的状态被添加到了mSavedState中        mSavedState.set(position, fragment.isAdded()                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);      //mFragment的position处置空        mFragments.set(position, null);    //递交一个remove事务        mCurTransaction.remove(fragment);    }

看到FragmentManager.saveFragmentInstanceState(fragment),这个方法返回了Fragment.SavedState对象,而将saveFragmentBasicState(fragment)的返回值作为参数传递给了Fragment.SavedState的构造器,saveFragmentBasicState(fragment)的大致作用已经注释。也就是说FragmentStatePagerAdapter的mSavedState保存了将要被移除的Fragment的这些信息。

    @Override    public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) {        ...        if (fragment.mState > Fragment.INITIALIZING) {          //这个地方保存了Fragment的自定义变量,子Fragment信息,视图信息等            Bundle result = saveFragmentBasicState(fragment);            //将Bundle放进  Fragment.SavedState中            return result != null ? new Fragment.SavedState(result) : null;        }        return null;    }

接着看destroyItem()中,将mFragments的position处的引用置为null后,递交一个remove事务。还是最终会调用到FragmentManager的removeFragment(),这里首先获取了是否是inactive的,最终根据inactive判断Fragment降级同步的终点,然后mAdded列表中删除了该Fragment,Fragment的mAdded = false。
说一下这里一般是不在BackStack中的,所以会降级到INITIALIZING,降级的过程中回调了onDestroy和onDetach,最终该Fragment会从FragmentManager的mActive删除,并且所有值都初始化为默认值。

    public void removeFragment(Fragment fragment, int transition, int transitionStyle) {        //这里一般为true        final boolean inactive = !fragment.isInBackStack();        if (!fragment.mDetached || inactive) {            if (mAdded != null) {                mAdded.remove(fragment);            }            if (fragment.mHasMenu && fragment.mMenuVisible) {                mNeedMenuInvalidate = true;            }            fragment.mAdded = false;            fragment.mRemoving = true;          //关键            moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,                    transition, transitionStyle, false);        }    }

也就是说destroyItem()调用后Fragment的一些状态被保存了下来(包括视图)作为持久化数据,那mFragments有什么用呢?
我们接着看instantiateItem()。详细注释在下面。起初mFragments肯定是空,mSavedState也是空,Fragment来自于我们自建的Fragment列表,缓存区域的Fragment会被添加到FragmentManager中,接着进行升级同步,这和FragmentPagerAdapter一样。但一旦已经添加的Fragment移出了缓存区域,它会被保存一些状态并彻底从FragmentManager中移除(经历onDestroy和onDetach),同时本身也变为一个空壳,也就是说FragmentManager(的mAdded和mActive)并不持有这个Fragment的引用了,而且我们自建列表持有的是一个空壳。实际上除了第一次初始化,再次移动到某个位置时,我们都是用空壳和保存的状态升级同步了一个Fragment,那么肯定会经历onAttach和onCreate。(实验了一下,生命周期和上述吻合,另外说一下TextView要设置freezesText属性才能保存状态,一开始不知道还以为上面都分析错了= =)。

    @Override    public Object instantiateItem(ViewGroup container, int position) {        // If we already have this item instantiated, there is nothing        // to do.  This can happen when we are restoring the entire pager        // from its saved state, where the fragment manager has already        // taken care of restoring the fragments we previously had instantiated.      //在mFragments寻找,这种情况发生在Activity的recreate后,有兴趣可以看下。        if (mFragments.size() > position) {            Fragment f = mFragments.get(position);            if (f != null) {                return f;            }        }        //从Adapter中获取Fragment        Fragment fragment = getItem(position);       //如果有这个位置的状态,为这个Fragment设置获得的状态,这会在升级同步中用到。        if (mSavedState.size() > position) {            Fragment.SavedState fss = mSavedState.get(position);            if (fss != null) {                fragment.setInitialSavedState(fss);            }        }        while (mFragments.size() <= position) {            mFragments.add(null);        }        //保存一份在mFragments中        mFragments.set(position, fragment);      //递交add事务        mCurTransaction.add(container.getId(), fragment);        return fragment;    }

再补充一点,mFragments似乎没有被用到,其实它是用来在Activity发生recreate情况下进行状态保存用的,也就是说只保存缓存区域内的状态。具体是和FragmentStatePagerAdapter中的saveState()和restoreState()方法有关。

总结

我们要明确的是Fragment的生命周期始终是由FragmentManager管理的,ViewPager的populate()会影响超出和进入缓存区域的Fragment的生命周期,因为它会利用FragmentManager递交对应的事务。

对于FragmentPagerAdapter,instantiateItem()中的Fragment(除了第一次)都是来源于FragmentManager的mActive中,状态是CREATED,然后递交attach事务进行升级同步;destroyItem()递交detach事务,作用是让Fragment只保存在FragmentManager的mActive中,并且状态降级为CREATED。attach和detach的配合,让Fragment的视图进行了连接和拆除。

对于FragmentStatePagerAdapter,instantiateItem()中的Fragment(除了第一次)都是来源于“空壳”列表和mSavedState这个成员保存的持久化数据的合成(在之后的同步中),起始状态是INITIALIZING,递交的是add事务;destroyItem()中保存了Fragment的BasicState(包括视图状态),递交remove事务,Fragment被降级为INITIALZING。add和remove的配合,让Fragment经历了整个生命周期。

那按照我的理解,除去缓存区域之外,FragmentPagerAdapter保存了一些没有视图的Fragment,而FragmentStatePagerAdapter的mSavedState成员保存了一些可持久化的BasicState。那么除了内容上的不同还有什么区别呢?答案应该在ViewPager的里面。

//Viewpager.java@Override    public Parcelable onSaveInstanceState() {        Parcelable superState = super.onSaveInstanceState();        SavedState ss = new SavedState(superState);        ss.position = mCurItem;        if (mAdapter != null) {          //这里            ss.adapterState = mAdapter.saveState();        }        return ss;    }

我们知道Activity有recreate需求的时候,会去保存所有View的状态。当ViewPager的这个方法被调用时。会去调用Adapter的相应方法。

看一下FragmentPagerAdapter和FragmentStatePagerAdapter的对应方法应该就明了了

//FragmentPagerAdapter.java@Override    public Parcelable saveState() {        return null;    }//FragmentStatePagerAdapter.java    @Override    public Parcelable saveState() {        Bundle state = null;        if (mSavedState.size() > 0) {            state = new Bundle();            Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];            mSavedState.toArray(fss);            state.putParcelableArray("states", fss);        }        for (int i=0; i<mFragments.size(); i++) {            Fragment f = mFragments.get(i);            if (f != null && f.isAdded()) {                if (state == null) {                    state = new Bundle();                }                String key = "f" + i;                mFragmentManager.putFragment(state, key, f);            }        }        return state;    }
阅读全文
0 0