FragmentStatePagerAdapter与FragmentPagerAdapter(开发所遇到的坑)

最近碰到个比较奇葩的需求,总结问题就是ViewPager和Fragment搭配使用(3种以上类型的Fragment),Fragment列表中的不同种Fragment顺序改变,或者删除增加导致的各种崩溃问题,不论使用FragmentPagerAdapter还是FragmentStatePagerAdapter都会出现不同类型的错误,也让我看到了这两种PageAdapter的局限性,在此总结,希望能帮到遇到同样问题的亲们。

本篇内容

一、FragmentPagerAdapter和FragmentStatePagerAdapter的区别以及局限性

二、由于ViewPager中Fragment列表的增删和顺序变化,导致ViewPager调用notifyDataSetChanged()方法出现异常或应用崩溃的解决方案

————————————————————————————————

内容一:FragmentPagerAdapter和FragmentStatePagerAdapter的区别以及局限性

FragmentPagerAdapter和FragmentStatePagerAdapter,这两个adapter刚开始用的时候分不清,不过似乎不论用哪个adapter出来的效果都差不多,用的多的人知道,当viewpager中fragment数量多的时候用FragmentStatePagerAdapter,反之则用FragmentPagerAdapter。在这里我想从两个问题展开,看清这两个问题,大家就一目了然了:

1.两种adapter存储/恢复fragment的区别

2.两种adapter销毁fragment的区别

存储和恢复:

FragmentPagerAdapter

初始化方法:


@Override

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 fragment = mFragmentManager.findFragmentByTag(name);//从manager中找出该名字的fragment

    if (fragment != null) {

        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);

        mCurTransaction.attach(fragment);

    } else {

        /*

      继承FragmentPagerAdapter要实现的getItem方法,从源码中可以看到,

      不是viewpager跳转到哪个fragment就会调用getItem方法,

      而是只要manager中没有这个fragment才会调用,

      一个fragment只会调用一次这个方法

      */

        fragment = getItem(position);

        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);

        mCurTransaction.add(container.getId(), fragment,

                makeFragmentName(container.getId(), itemId));//将命名的fragment放入manager进行管理

    }

    if (fragment != mCurrentPrimaryItem) {

        fragment.setMenuVisibility(false);

        fragment.setUserVisibleHint(false);

    }

    return fragment;

}


以上代码要注意两点,一个是getItem方法的调用时机,只有当manager中没有该Fragment的时候才会被调用,一个Fragment只会触发这一次,原因就是要注意的第二点,方法makeFragmentName(),通过父容器container的Id和getItemId()方法返回的值(不重写该方法就返回fragment的position值),为每一个fragment命名,然后通过键值对的方式,放入到manager中,从而达到存储fragment和恢复fragment的目的。然而这个makeFragmentName()也就是当fragment列表顺序改变(即position值改变)导致崩溃的原因。

因此这个FragmentPagerAdapter不适用于fragment列表顺序改变,中间添加fragment或者删除某个fragment的情形。

销毁方法:

@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);//仅detach该fragment 没做任何释放内存操作

}


这是销毁fragment的方法,可以看到FragmentPagerAdapter不是将不可见的fragment销毁,而是仅仅将该fragment从页面中detach掉,fragment还是在manager中保存,内存没有被释放,从这边可以看到FragmentPagerAdapter不适合fragment数量多的情况下使用,因为未被释放的fragment会占用大量内存。

FragmentStatePagerAdapter

初始化方法

/*

  FragmentStatePagerAdapter是通过mFragments数组来存储fragment的,通过mSavedState列表

  来存储fragment销毁时的状态,通过position获取到的fragment可能为空(被回收),如果为空,则会

  再次调用getItem方法重新创建新的fragment,然后将mSavedState中存储的状态重新赋予这个新的fragment,

  达到fragment恢复的效果

*/

@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.

    if (mFragments.size() > position) {

        Fragment f = mFragments.get(position);

        if (f != null) {

            return f;

        }

    }

    if (mCurTransaction == null) {

        mCurTransaction = mFragmentManager.beginTransaction();

    }

    Fragment fragment = getItem(position);//创建fragment 因为fragment频繁的创建导致这个getItem方法会被多次调用 区别于FragmentPagerAdapter

    if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);

    if (mSavedState.size() > position) {

        Fragment.SavedState fss = mSavedState.get(position);//从存储fragment的列表中取出对应位置fragment保存的状态

        if (fss != null) {

            fragment.setInitialSavedState(fss);//将之前fragment状态赋予新的fragment

        }

    }

    while (mFragments.size() <= position) {

        mFragments.add(null);

    }

    fragment.setMenuVisibility(false);

    fragment.setUserVisibleHint(false);

    mFragments.set(position, fragment);

    mCurTransaction.add(container.getId(), fragment);

    return fragment;

}


以上代码的中mFragments和mSavedState是按顺序一一对应的,每当一个fragment重建都会从mSavedState中找到相应的状态,如果mFragment中的某一个fragment顺序改变,那么mFragments和mSavedState就不再一一对应,导致fragment恢复时出现异常情况。

同样这个FragmentStatePagerAdapter不适用于fragment列表顺序改变,中间添加fragment或者删除某个fragment的情形。

销毁方法:

Override

public void destroyItem(ViewGroup container, int position, Object object) {

    Fragment fragment = (Fragment) object;

    if (mCurTransaction == null) {

        mCurTransaction = mFragmentManager.beginTransaction();

    }

    if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object

            + " v=" + ((Fragment)object).getView());

    while (mSavedState.size() <= position) {

        mSavedState.add(null);

    }

    mSavedState.set(position, fragment.isAdded()

            ? mFragmentManager.saveFragmentInstanceState(fragment) : null);//释放引用之前保存该位置fragment的状态

    mFragments.set(position, null);//将mFragment列表中该位置的fragment至空,释放引用

    mCurTransaction.remove(fragment);//同时将manager中的该fragment释放

}


FragmentPagerAdapter销毁fragment方法,可以看到,当fragment在页面中不可见时,该fragment的状态会先被保存到mSavedState中,而fragment实例则会被销毁,在对应的instantiateItem方法中,fragment会被重新创建,并将mSavedState中对应状态赋予该刚刚创建的新fragment,从而达到恢复之前fragment和节省内存的效果,因此FragmentStatePagerAdapter适合有较多fragment情况

————————————————————————————————

内容二,由于ViewPager中Fragment列表的增删和顺序变化,导致ViewPager调用notifyDataSetChanged()方法出现异常或应用崩溃的解决方案.

看完以上源码,相信大家应该明白,发生fragment列表顺序改变,中间添加fragment或者删除某个fragment这些情况的时候,为什么会出现异常等情况。我们需要根据需求,定制这两种不同的adapter,所以在此给出发生这些异常时的解决思路:

1、若Fragment数量较少,可采用FragmentPagerAdapter的方式,要做的是定制makeFragmentName()该方法,让instantiateItem获取fragment时不根据position和itemid值,而是根据你的需求对fragment进行命名,同时当fragment列表数量减少时,从manager中删除被删除的fragment引用,让其内存得到释放。

2、若Fragment数量较多,可采用FragmentStatePagerAdapter的方式,要做的是改变其中mFragments和mSavedState的对应关系,不再根据position进行一一对应。

(我说的方式当然是把两个adapter的源码复制一遍然后修改啦!)

最后请注意重写adapter中的这个方法

@Override

public int getItemPosition(Object object) {

    //object参数是当前位置的fragment

    //return POSITION_UNCHANGED;

    //return POSITION_NONE;

}



果返回POSITION_UNCHANGED,那么该位置的fragment是不会被更新的,POSITION_NONE才会重新更新这个位置fragment

例子:如果要更新的fragment列表中第一个fragment是固定不变的,那么这个位置的fragment可以返回POSITION_UNCHANGED,而其他位置改变的fragment则要返回POSITION_NONE。

---------------------

viewpager+tablayout+fragment切换时嵌套复用的时候,fragment与对应显示内容不符时,使用FragmentStatePagerAdapter就可完美解决。。。。。。。。。

你可能感兴趣的:(FragmentStatePagerAdapter与FragmentPagerAdapter(开发所遇到的坑))