多层嵌套后的 Fragment 懒加载
印象中从 Feed 流应用流行开始,Fragment
懒加载变成了一个大家都需要关注的开发知识,关于 Fragment
的懒加载,网上有很多例子,GitHub 上也有很多例子,就连我自己在一年前也写过相关的文章。但是之前的应用可能最多的是一层 Activity + ViewPager
的 UI 层次,但是随着页面越来越复杂,越来越多的应用首页一个页面外层是一个 ViewPager
内部可能还嵌套着一层 ViewPager
,这是之前的懒加载就可能不那么好用了。本文对于多层 ViewPager
的嵌套使用过程中,Fragment
主要的三个状态:第一次可见,每次可见,每次不可见,提供解决方案。
为什么要使用懒加载
在我们开发中经常会使用 ViewPager + Fragment
来创建多 tab 的页面,此时在 ViewPager
内部默认会帮我们缓存当页面前后两个页面的 Fragment
内容,如果使用了 setOffscreenPageLimit
方法,那么 ViewPager
初始化的时候将会缓存对应参数个 Fragment。为了增加用户体验我们往往会使用该方法来保证加载过的页面不被销毁,并留离开 tab 之前的状态(列表滑动距离等),而我们在使用 Fragment
的时候往往在创建完 View
后,就会开始网络请求等操作,如果存在上述的需求时,懒加载就显得尤为重要了,不仅可以节省用户流量,还可以在提高应用性能,给用户带来更加的体验。
ViewPager + Fragment
的懒加载实质上我们就在做三件事,就可以将上边所说的效果实现,那么就是找到每个 Fragment
第一对用户可见的时机,和每次 Fragment 对用户可见时机,以及每次 Framgment
对用户不可见的时机,来暴露给实现实现类做对应的网络请求或者网络请求中断时机。下面我们就来从常见的几种 UI 结构上一步步实现无论嵌套多少层,无论开发者使用的 hide show
还是 ViewPager
嵌套都能准确获取这三种状态的时机的一种懒加载实现方案。
单层 ViewPager + Fragment 懒加载
我们都知道 Fragment 生命周期按先后顺序有
onAttach -> onCreate -> onCreatedView -> onActivityCreated -> onStart -> onResume -> onPause -> onStop -> onDestroyView -> onDestroy -> onDetach
对于 ViewPager + Fragment 的实现我们需要关注的几个生命周期有:
onCreatedView + onActivityCreated + onResume + onPause + onDestroyView
以及非生命周期函数:
setUserVisibleHint + onHiddenChanged
对于单层 ViewPager + Fragment 可能是我们最常用的页面结构了,如网易云音乐的首页顶部的是三个 tab ,我们那网易云音乐作为例子:
对于这种 ViewPager + Fragment 结构,我们使用的过程中一般只包含是 4 种情况分别是:
使用
FragmentPagerAdapter
,FragmentPagerStateAdapter
不设置setOffscreenPageLimit
- 左右滑动页面,每次只缓存下一个 Pager ,和上一个 Pager
- 间隔的点击 tab 如从位于 tab1 的时候直接选择 tab3 或 tab4
使用
FragmentPagerAdapter
,FragmentPagerStateAdapter
设置setOffscreenPageLimit
为 tab 总数- 左右滑动页面,每次只缓存下一个 Pager ,和上一个 Pager
- 间隔的点击 tab 如从位于 tab1 的时候直接选择 tab3 或 tab4
- 进入其他页面或者用户按 home 键回到桌面,当前 ViewPager 页面变成不见状态。
对于 FragmentPagerAdapter
和 FragmentPagerStateAdapter
的区别在于在于,前者在 Fragment
不见的时候将不会 detach
,而后者将会销毁 Fragment
并 detach
掉。
实际上这也是所有 ViewPager
的操作情况。
- 第一种情况不设置
setOffscreenPageLimit
左右滑动页面/或者每次选择相邻 tab 的情况FragmentPagerAdapter
和FragmentPagerStateAdapter
有所区别
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint true
BottomTabFragment1 onCreateView
BottomTabFragment1 onActivityCreated
BottomTabFragment1 onResume
BottomTabFragment2 onCreateView
BottomTabFragment2 onActivityCreated
BottomTabFragment2 onResume
//滑动到 Tab 2
BottomTabFragment3 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint true
BottomTabFragment3 onCreateView
BottomTabFragment3 onActivityCreated
BottomTabFragment3 onResume
//跳过 Tab3 直接选择 Tab4
BottomTabFragment4 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment4 setUserVisibleHint true
BottomTabFragment4 onCreateView
BottomTabFragment4 onActivityCreated
BottomTabFragment4 onResume
BottomTabFragment2 onPause
BottomTabFragment2 onDestroyView
// FragmentPagerStateAdapter 会走一下两个生命周期方法
BottomTabFragment2 onDestroy
BottomTabFragment2 onDetach
BottomTabFragment1 onPause
BottomTabFragment1 onDestroyView
// FragmentPagerStateAdapter 会走一下两个生命周期方法
BottomTabFragment1 onDestroy
BottomTabFragment1 onDetach
// 用户回到桌面 再回到当前 APP 打开其他页面当前页面的生命周期也是这样的
BottomTabFragment3 onPause
BottomTabFragment4 onPause
BottomTabFragment3 onStop
BottomTabFragment4 onStop
BottomTabFragment3 onResume
BottomTabFragment4 onResume
- 第二种情况设置 setOffscreenPageLimit 为 Pager 的个数时候,左右滑动页面/或者每次选择相邻 tab 的情况 FragmentPagerAdapter 和 FragmentPagerStateAdapter 没有区别
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment3 setUserVisibleHint false
BottomTabFragment4 setUserVisibleHint false
BottomTabFragment1 setUserVisibleHint true
BottomTabFragment1 onCreateView
BottomTabFragment1 onActivityCreated
BottomTabFragment1 onResume
BottomTabFragment2 onCreateView
BottomTabFragment2 onActivityCreated
BottomTabFragment3 onCreateView
BottomTabFragment3 onActivityCreated
BottomTabFragment4 onCreateView
BottomTabFragment4 onActivityCreated
BottomTabFragment2 onResume
BottomTabFragment3 onResume
BottomTabFragment4 onResume
//选择 Tab2
BottomTabFragment1 setUserVisibleHint false
BottomTabFragment2 setUserVisibleHint true
//跳过 Tab3 直接选择 Tab4
BottomTabFragment2 setUserVisibleHint false
BottomTabFragment4 setUserVisibleHint true
// 用户回到桌面 再回到当前 APP 打开其他页面当前页面的生命周期也是这样的
BottomTabFragment1 onPause
BottomTabFragment2 onPause
BottomTabFragment3 onPause
BottomTabFragment4 onPause
BottomTabFragment1 onResume
BottomTabFragment2 onResume
BottomTabFragment3 onResume
BottomTabFragment4 onResume
可以看出第一次执行 setUserVisibleHint(boolean isVisibleToUser)
除了可见的 Fragment 外都为 false,还可以看出除了这一点不同以外,所有的 Fragment 都走到了生命周期 onResume 阶段。而选择相邻 tab 的时候已经初始化完成的Fragment 并不再重新走生命周期方法,只是 setUserVisibleHint(boolean isVisibleToUser)
为 true。当用户进入其他页面的时候所有 ViewPager 缓存的 Fragment 都会调用 onPause 生命周期函数,当再次回到当前页面的时候都会调用 onResume。
能发现这一点,其实对于单层 ViewPager 嵌套 Fragment 可见状态的把握其实已经很明显了。下面给出我的解决方案:
- 对于 Fragment 可见状态的判断需要设置两个标志位 ,Fragment View 创建完成的标志位
isViewCreated
和Fragment
第一次创建的标志位mIsFirstVisible
- 为了获得 Fragment 不可见的状态,和再次回到可见状态的判断,我们还需要增加一个
currentVisibleState
标志位,该标志位在 onResume 中和 onPause 中结合getUserVisibleHint
的返回值来决定是否应该回调可见和不可见状态函数。
整个可见过程判断逻辑如下图所示
接下来我们就来看下具体实现&#