前言

Fragment在ViewPager中的正确应用(2)内存泄漏?内存溢出

错误终结者:Fragment在ViewPager中的正确应用

OK,填坑篇的文章来了。

当我打开官方文档准备开始了解FragmentStatePagerAdapter的时候。我仿佛像是…闭关蛰伏数十载,准备反清复明;出关时发现大清已经亡了…


什么鬼,我还不会用呢,就tm废弃了???

正文

当然这不妨碍咱们去了解它如何增强了FragmentPagerAdapter。扶我起来,我还能学!

看FragmentStatePagerAdapter之前,咱们还是要先看文档。官网是这么介绍这个类的(我直接用自己蹩脚的英文翻译了一下):

当存在大量fragment时,此版本的更加高效。当Fragment对用户不可见时,它们的整个Fragment可能会被destory,仅保留该Fragment的状态。与FragmentPagerAdapter相比会占用更少的内存。

它的用法和FragmentPagerAdapter(以下简称FPA)一模一样,这里就不展开了。大家有兴趣可以直接看文档中的demo。

从文档介绍来看,FragmentStatePagerAdapter提供更少的内存开销。第二篇文章,咱们也已经明白了FragmentPagerAdapter在FragmentManager体系下会可能出现大量内存消耗的问题。那么咱们就来看看,FragmentStatePagerAdapter是如何优化这个问题。

一、如果做到更少的内存开销?

FragmentStatePagerAdapter(以下简称FSPA)的实现比较的简单,解决方式也很简单粗暴。咱们先看一个关键的方法instantiateItem(),基于这个方法咱们分4步来看一下这里的实现原理:

@Overridepublic Object instantiateItem(@NonNull ViewGroup container, int position) {    // 步骤1    if (mFragments.size() > position) {        Fragment f = mFragments.get(position);        if (f != null) {            return f;        }    }    // 省略代码    // 步骤2    Fragment fragment = getItem(position);    if (mSavedState.size() > position) {        Fragment.SavedState fss = mSavedState.get(position);        if (fss != null) {            fragment.setInitialSavedState(fss);        }    }    // 步骤 3    while (mFragments.size() <= position) {        mFragments.add(null);    }    // 省略部分代码    // 步骤4    mFragments.set(position, fragment);    mCurTransaction.add(container.getId(), fragment);    // 省略部分代码    return fragment;}

我们可以看到这里的instantiateItem()和FPA有着极大的不同:这里没有通过FragmentManager去find已经存在的Fragment!这里可以断定FSPA失去了FPA上缓存的逻辑,接下来咱们会通过FSPA的源码来进一步了解二者逻辑上的不同。

1.1、步骤一分析

步骤1中的mFragments是Adapter里的局部变量private ArrayList mFragments = new ArrayList<>(),看到着我们第一想法就能够明白FSPA对Fragment的管理,在FragmentManager的基础上包了一层。

这里的处理也很简单粗暴,如果基于position能在mFragments中找到Fragment就直接return。这里有一个点,我们需要注意,这里是直接return。也就是意味着被mFragment持有的Fragment实例是没有从FragmentManager中detach的,因此不需要重新走状态。

此外需要留意的一点是:if (f != null),意味着mFragments里是有可能为null的,所以我们可以猜测mFragments对Fragment也是一个动态变化的持有关系。

1.2、步骤二分析

很熟悉的方法调用,找不到缓存的Fragment,调getItem(),交给实现方自行初始化Fragment。

然后基于mSavedState对当前Fragment执行一次initSavedState操作。

这里可能有小伙伴会有疑问,新new出来的Fragment为啥有可能会有SavedState呢?

针对这个问题,先简单解释一下(大家可以在后文中得到详细答案):因为这个mSavedState会存在所有实例过的Fragment的状态,但是mFragments里仅仅会存放当前attach的Fragment。因此调用getItem()时初始化的Fragment是有可能之前初始化过,因此这种case下是要恢复其状态的。

1.3、步骤三分析

步骤三做的事情就比较有趣了:

while (mFragments.size() <= position) {    mFragments.add(null);}

说白了就是在占位。看到这一步,咱们就能明白:mFragments就是一个“以position为key,fragment为value的Map”。

当我们定位到一个很靠后的position时。那么代码走到这我们得到的mFragments的List很有可能是这样的 :[fragment1,fragment2,null,null,null,接下来要被add的fragment6]

1.4、步骤四分析

步骤四就很简单了,add我们getItem出来的Fragment。

看完这四步,咱们大概也会发现代码并没有什么难的,虽然我们只看了一个方法,但是基本可以猜出FSPA的原理:

  • 只缓存当前attach上的Fragment

  • 缓存所有attach过Fragment的SaveState,以便重新new时的状态恢复

看起来是因为缓存的Fragment数量少了所以内存开销变少了…不过我猜有同学这个时候会提出疑问:即使FSPA里mFragments缓存的Fragment少了,但是FragmentStore里该缓存还是要缓存的啊,这么一看,FSPA甚至多缓存了一份!

接下来咱们就要看另一个方法了,看看FSPA如果解决上述的问题。

二、销毁Fragment

其实有了第二篇文章的分析,咱们已经明确是FragmentManager内存爆炸的原因就是在于FragmentStore在mActive中强引用了所有的Fragment实例,不进行任何回收。

既然FSPA号称更少的开销,那么势必要直面这个问题。所以接下来就让咱们看看,FSPA销毁Fragment的策略。

2.1、destroyItem()

FSPA和FPA主要区别就在于对destroyItem()的实现。这里咱们先对比一下二者的实现:

// FSPA@Overridepublic void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {    Fragment fragment = (Fragment) object;

    if (mCurTransaction == null) {        mCurTransaction = mFragmentManager.beginTransaction();    }    while (mSavedState.size() <= position) {        mSavedState.add(null);    }    mSavedState.set(position, fragment.isAdded()            ? mFragmentManager.saveFragmentInstanceState(fragment) : null);    mFragments.set(position, null);    // 注意这里    mCurTransaction.remove(fragment);    if (fragment.equals(mCurrentPrimaryItem)) {        mCurrentPrimaryItem = null;    }}

// FPA@Overridepublic void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {    Fragment fragment = (Fragment) object;

    if (mCurTransaction == null) {        mCurTransaction = mFragmentManager.beginTransaction();    }    // 注意这里    mCurTransaction.detach(fragment);    if (fragment.equals(mCurrentPrimaryItem)) {        mCurrentPrimaryItem = null;    }}

FSPA调用了remove方法,而FPA调用的是detach方法。接下来咱们就看看这二者有什么不同。其实无论是remove还是detach都会走到executeOps()中的switch判断:

 case OP_REMOVE:    f.setNextAnim(op.mExitAnim);    mManager.removeFragment(f);    break;

case OP_DETACH:    f.setNextAnim(op.mExitAnim);    mManager.detachFragment(f);    break;

但是这里无论是removeFrament()还是detachFragment()。本质调的都是mFragmentStore.removeFragment(fragment);,这里是把当前Fragment从FragmentStore中的mAdded列表移除还不会动mActive列表。

因此对于FSPA来说,它并不是通过这种方式来控制内存开销。咱们继续往下看…

2.2、控制Fragment的状态机

上述switch判断结束后,才会走到真正驱动状态的地方:

if (!mReorderingAllowed && op.mCmd != OP_ADD && f != null) {    // 这里边会走到moveToState()中    mManager.moveFragmentToExpectedState(f);}

FragmentManager#moveToState()

if (f.mState <= newState) {    switch (f.mState) {        case Fragment.INITIALIZING:{            if (newState > Fragment.INITIALIZING) {                // 省略部分代码            }        }        case Fragment.ATTACHED:{            // 省略部分代码        }        // 省略部分代码}else if (f.mState > newState) {    switch (f.mState) {        case Fragment.RESUMED:            if (newState                 // 省略部分代码            }        case Fragment.CREATED:            if (newState                 // 重点在这                boolean beingRemoved = f.mRemoving && !f.isInBackStack();                if (beingRemoved || mNonConfig.shouldDestroy(f)) {                    makeInactive(fragmentStateManager);                }                // 省略部分代码            }        // 省略部分代码    }    // 省略部分代码}

这里状态机的逻辑,大家有兴趣可以自己阅读一下。这里处理状态的逻辑还是挺“骚”的。咱们只关注makeInactive()。上文我们之后remove和detach的区别,而这个区别的分水岭就在于这个方法。remove是会走到这个方法中:

private void makeInactive(@NonNull FragmentStateManager fragmentStateManager) {    // 省略部分代码    mFragmentStore.makeInactive(fragmentStateManager);    removeRetainedFragment(f);}

void makeInactive(@NonNull FragmentStateManager newlyInactive) {    Fragment f = newlyInactive.getFragment();    for (FragmentStateManager fragmentStateManager : mActive.values()) {        if (fragmentStateManager != null) {            Fragment fragment = fragmentStateManager.getFragment();            if (f.mWho.equals(fragment.mTargetWho)) {                fragment.mTarget = f;                fragment.mTargetWho = null;            }        }    }

    mActive.put(f.mWho, null);

    if (f.mTargetWho != null) {        f.mTarget = findActiveFragment(f.mTargetWho);    }}

可以看到makeInactive()方法中会对mActive进行回收的操作。因此FSPA比FPA的优化就在于移除掉了对mActive中“不必要”的引用。

我猜看到这大家应该就能够get到FSPA的优化点,不过…问题来了:既然把FragmentManager中mActive移除掉了,那我们的缓存呢?

三、失去了缓存

事实的确如此,咱们在开篇看instantiateItem()实现的时候就已经发现,FSPA移除了通过FragmentManager去find缓存的逻辑。

咱们基于之前的文章,可以明白FPA的缓存是基于FragmentManager的mActive缓存,也明白FPA内存溢出也是因为FragmentManager的mActive缓存。

因此FSPA的优化原理也很好理解,在FragmentManager中移除掉了mActive的缓存。

这里也就意味着,FSPA和FPA有一些不同:

  • 1、只要不在mAdd的Fragment,FSPA都会走getItem()去new Fragment。

  • 2、我们没办法方便的基于FragmentManager去拿到我们想要得到的Fragment实例。(FSPA是基于id去把Fragment添加到mAdd)

3.1、ViewPager中取特定Fragment实例是否合理

这里咱们多聊一句。不知道大家有没有发现,无论上FPA还是FSPA,Google都没有主动提供获取内部持有Fragment的public方法。甚至在FSPA中,移除了任何这种操作的可能性。

如果单纯从这个现象来看,基于ViewPager去变相的获取内部Fragment是一个“不合理”的操作。但是咱们也很清楚需求这种东西,如果都“合理”那就不叫需求了…因此这种操作是无法避免的。所有,咱们需要从FSPA和FPA的不同点来明确咱们该用谁…

  • 如果我们需要FragmentManager去缓存我们的Fragment那么FPA是一个不错的选择。

  • 如果我们拥有大量的Fragment在ViewPager中,那么FSPA是一个不错的选择。

当然鉴于FSPA已经被废弃了,咱们项目中首选还是ViewPager2。关于ViewPager2的分析会在后续放出…

尾声

算上今天的文章,关于Fragment在ViewPager中应用的文章已经三篇了。

尽可能的学的深入,尽可能的发布正确的文章。欢迎大家评论区一起讨论~

viewpager初始化fragment没有绘制_Fragment在ViewPager中的正确应用(3)FragmentStatePagerAdapter优化了什么...相关推荐

  1. viewpager初始化fragment没有绘制_NDK OpenGL ES渲染系列 之 绘制三角形

    前言 新的知识学习都是循序渐进的,从基础到复杂.前面OpenGL ES概念 已经介绍了OpenGL ES的相关概念,这篇文章开始我们就正式开始OpenGL ES渲染系列第一站---绘制三角形.绘制三角 ...

  2. Android ViewPager和Fragment实现顶部导航界面滑动效果

    在项目中,我们常常需要实现界面滑动切换的效果.例如,微信界面的左右滑动切换效果.那这种效果是怎么实现的?今天我就带大家简单了解ViewPager,并通过实例来实现该效果. 一. ViewPager 官 ...

  3. android中viewpager+fragment,ViewPager和Fragment一篇就够了

    ViewPager显示多Fragment使用问题 前言:每当使用ViewPager时,对于选用什么适配器,缓存多少页面,是否需要懒加载以及Fragment的数据刷新经常会有些疑问,网络上的答案很多,但 ...

  4. Android开发之ViewPager+ActionBar+Fragment实现响应式可滑动Tab

    今天我们要实现的这个效果呢,在Android的应用中十分地常见,我们可以看到下面两张图,无论是系统内置的联系人应用,还是AnyView的阅读器应用,我们总能找到这样的影子,当我们滑动屏幕时,Tab可以 ...

  5. 【Android】Fragment懒加载和ViewPager的坑

    本篇文章已授权微信公众号 安卓巴士Android开发者门户 独家发布 效果 老规矩,先来看看效果 ANDROID和福利两个Fragment是设置的Fragment可见时加载数据,也就是懒加载.圆形的旋 ...

  6. ViewPager切换Fragment生命周期变化

    很久没有写博客了,今天学习了MD的TabLayout ,使用到了ViewPager,于是对于很久以来的一个疑问:ViewPager切换Fragment生命周期是如何变化的进行了一个测试. 本文研究了: ...

  7. viewPager嵌套fragment

    *第一站效果图  *滑动到第三种的效果图 第一步:适配器的创建(就是viewpager的adapter) 分析:1,这里有三个函数,根据第一部分的官方文档,可知,对于FragmentPagerAdap ...

  8. ViewPager和Fragment的嵌套使用

    老规矩先贴效果图,最后给Demo的ZIP提供下载 *第一站效果图 *滑动到第三种的效果图 第一步:适配器的创建(就是viewpager的adapter) 分析:1,这里有三个函数,根据第一部分的官方文 ...

  9. android viewpager 嵌套fragment,Android ViewPager+Fragment多层嵌套(使用问题处理)

    之前写了Android ViewPager+Fragment(使用问题处理),封装了一个BaseFragment,对于简单使用ViewPager+Fragment而言,是没有问题的. 不过,ViewP ...

最新文章

  1. 用eclipse生成可运行jar包、启动jar包及常见错误
  2. 打包phar文件过大的问题。
  3. Error: Cannot find module ‘node-sass‘---Node-sass下载
  4. 如何在本地管理和切换多个 github 账号?
  5. [WSE]如何启用WSE2.0的强大的Trace功能
  6. Android开发笔记(一百一十三)测试工具
  7. 一文弄懂特征缩放(归一化/正则化)
  8. 嗯嗯------摘抄
  9. 如何自动申请京东试用商品、签到获取京豆
  10. 在IMX6Q的linux4.1.15版本上默认支持docker
  11. PgAdmin3 LST v1.23.0.b (BigSQL) which support pg10.4
  12. 怎么看《就算老公一毛钱股份都没拿到,在我心里,他依然是最牛逼的创业者》文中创业公司 CEO 的行为?
  13. 编程数学-∑(求和符号)-Sigma
  14. 读书笔记-人际网络的中心
  15. elementUI 表格宽度自适应、不换行
  16. 使用webots的MPC的移动机器人轨迹跟踪控制
  17. Doctype作用? 严格模式与混杂模式如何区分?它们有何意义?Doctype文档类型?
  18. 畜牧业的论文发表一般多少钱
  19. 用免費的電腦資源協助數學的教學,學習與探索_復華中學教師營_中山大學應數系高中數學人才班_2021
  20. OpenCV——图像特征提取(颜色:HSV与形状)

热门文章

  1. 生物医学大数据处理研究探讨
  2. Chimera 嵌合体
  3. 厦大计算机研究生和福大,考研:只知道厦门大学?福建还有这些211值得你了解...
  4. JAVA基础6-函数
  5. db2安装包v10.5_LINUX安装db2V10.5步骤
  6. tf.keras.optimizers.Adam 优化器 示例
  7. js 动态 添加 tabel 表格
  8. python 类 公有属性、私有属性、公有方法、私有方法
  9. 六、线程的实现方式---多线程模型
  10. ASCII,Unicode和UTF-8终于找到一个能完全搞清楚的文章了