美文网首页
FragmentStatePagerAdapter与Fragme

FragmentStatePagerAdapter与Fragme

作者: y_2dbc | 来源:发表于2019-03-07 22:57 被阅读0次

    最近碰到个比较奇葩的需求,总结问题就是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与Fragme

          本文链接:https://www.haomeiwen.com/subject/xwpkpqtx.html