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就可完美解决。。。。。。。。。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • ViewPager在开发中的使用频率非常的高,所以在此做个总结。主要包括以下几方面: ViewPager的简介和作...
    西瓜太郎123阅读 120,789评论 21 261
  • FragmentPagerAdapter的三个调用方法 为了解决不同ViewPage 和Fragment之间切换时...
    簡康阅读 1,429评论 0 3
  • 在使用ViewPager常用设置1)mViewPager.setOffscreenPageLimit(2);//设...
    kangqiao182阅读 7,663评论 2 12
  • 拨草。 非得等到五点半再营业,有原则的店呐。 一锅够三个人吃!好吃!但没想象中好吃 新品,还不错,可以免费加面 好...
    菠00阅读 407评论 0 0
  • 独自一人 走在无人的街道 品味着寒风吹来的孤寂 一旁昏黄的光 给世界遮上了纱帐 使眼前的道路更加模糊 却不知 看不...
    冬日的雨阅读 1,322评论 0 3