# 前言
OK,填坑篇的文章来了。
当我打开官方文档准备开始了解 FragmentStatePagerAdapter 的时候。我仿佛像是… 闭关蛰伏数十载,准备反清复明;出关时发现大清已经亡了…
什么鬼,我还不会用呢,就 tm 废弃了???
# 正文
当然这不妨碍咱们去了解它如何增强了 FragmentPagerAdapter。扶我起来,我还能学!
看 FragmentStatePagerAdapter 之前,咱们还是要先看文档
官网是这么介绍这个类的(我直接用自己蹩脚的英文翻译了一下):
当存在大量 fragment 时,此版本的更加高效。当 Fragment 对用户不可见时,它们的整个 Fragment 可能会被 destory,仅保留该 Fragment 的状态。与 FragmentPagerAdapter 相比会占用更少的内存。
它的用法和 FragmentPagerAdapter(以下简称 FPA)一模一样,这里就不展开了。大家有兴趣可以直接看文档中的 demo。
从文档介绍来看,FragmentStatePagerAdapter 提供更少的内存开销。第二篇文章,咱们也已经明白了 FragmentPagerAdapter 在 FragmentManager 体系下会可能出现大量内存消耗的问题。那么咱们就来看看,FragmentStatePagerAdapter 是如何优化这个问题。
# 一、如果做到更少的内存开销?
FragmentStatePagerAdapter(以下简称 FSPA)的实现比较的简单,解决方式也很简单粗暴。咱们先看一个关键的方法 instantiateItem (),基于这个方法咱们分 4 步来看一下这里的实现原理:
1 |
|
我们可以看到这里的 instantiateItem () 和 FPA 有着极大的不同:这里没有通过 FragmentManager 去 find 已经存在的 Fragment!这里可以断定 FSPA 失去了 FPA 上缓存的逻辑,接下来咱们会通过 FSPA 的源码来进一步了解二者逻辑上的不同。
# 1.1、步骤一分析
步骤 1 中的 mFragments 是 Adapter 里的局部变量 private ArrayList<Fragment> 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、步骤三分析
步骤三做的事情就比较有趣了:
1 | while (mFragments.size() <= position) { |
说白了就是在占位。看到这一步,咱们就能明白: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 () 的实现。这里咱们先对比一下二者的实现:
1 | // FSPA |
FSPA 调用了 remove 方法,而 FPA 调用的是 detach 方法。接下来咱们就看看这二者有什么不同。其实无论是 remove 还是 detach 都会走到 executeOps () 中的 switch 判断:
1 | case OP_REMOVE: |
但是这里无论是 removeFrament () 还是 detachFragment ()。本质调的都是 mFragmentStore.removeFragment(fragment);
,这里是把当前 Fragment 从 FragmentStore 中的 mAdded 列表移除还不会动 mActive 列表。
因此对于 FSPA 来说,它并不是通过这种方式来控制内存开销。咱们继续往下看…
# 2.2、控制 Fragment 的状态机
上述 switch 判断结束后,才会走到真正驱动状态的地方:
1 | if (!mReorderingAllowed && op.mCmd != OP_ADD && f != null) { |
这里状态机的逻辑,大家有兴趣可以自己阅读一下。这里处理状态的逻辑还是挺 “骚” 的。咱们只关注 makeInactive()
。上文我们之后 remove 和 detach 的区别,而这个区别的分水岭就在于这个方法。remove 是会走到这个方法中:
1 | private void makeInactive(@NonNull FragmentStateManager fragmentStateManager) { |
可以看到 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 的分析会在后续放出…
# 尾声
尽可能的学的深入,尽可能的发布正确的文章。欢迎大家评论区一起讨论~