ViewPager源码解析之FragmentPagerAdapter和FragmentStatePagerAdapter

从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.java
void 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的生命周期会交替进行,希望懂的朋友指点一下)

ViewPager源码解析之FragmentPagerAdapter和FragmentStatePagerAdapter_第1张图片
图1

图2是从1滑动到2,此时3开始初始化。
ViewPager源码解析之FragmentPagerAdapter和FragmentStatePagerAdapter_第2张图片
图2

图3是2滑动到3,此时1超出缓存范围,舍弃,4进入缓存范围,初始化。
ViewPager源码解析之FragmentPagerAdapter和FragmentStatePagerAdapter_第3张图片
图3

图4是由3滑动到2,此时1进入缓存范围,更新到最新状态,4超出缓存范围,舍弃。
ViewPager源码解析之FragmentPagerAdapter和FragmentStatePagerAdapter_第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.java
public 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.java
private ArrayList mSavedState = new ArrayList();
private ArrayList mFragments = new ArrayList();

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

你可能感兴趣的:(Android基础)