前言
前段时间在自己的练习项目中想用到懒加载机制,查看了大多数资料只介绍了在 View Pager + Fragment 组合的情况下实现的懒加载,但是现在大多数App更多的是 Fragmentmanager 去管理主页面多个 Fragment 的显示与隐藏,然后主界面的某个或多个 Fragment 里又嵌套了多个 Fragment + ViewPager (详细见下图 ),对于这种情况,适用于第一种的方式是不能直接解决第二种的情况的,所以写下这篇文章,记录一下踩的几个坑,希望对同像我一样的初学者提供一种思考方式作为参考(如果有错误或者不合适的地方,希望各位前辈能在评论区指出,非常感谢!)。
关于懒加载
1. 什么是懒加载?
懒加载也叫延迟加载,在APP中指的是每次只加载当前页面,是一种很好的优化APP性能的一种方式。
2.为什么要用懒加载?
优化APP性能,提升用户体验 :如果用户打开某页面,就会去预加载其它的页面时,数据集较小或者网络性能较优时还好,但是如果数据集过大或者网络性能不佳时,就会造成用户等待的时间较长,APP界面产生明显的滞顿感的情况,严重影响到用户的体验。
减少无效资源的加载,减少服务器的压力,节省用户流量 :如果用户只想浏览或者经常浏览某个特定的页面,如果使用预加载的方式,就会造成资源浪费,增加服务器的压力等。
实现懒加载
1.ViewPager+Fragment情况
1.1遇到的问题
在我们平时开发中,经常使用 ViewPager+Fragment 的组合来实现左右滑动的页面设计(如上图),但是 ViewPger 有个 预加载 机制,默认会把 ViewPager 当前位置的左右相邻页面预先初始化(俗称预加载),即使设置 setOffscreenPageLimit(0) 也无效果,也会预加载。通过点进源码中发现,如果不主动设置 setOffscreenPageLimit() 方法, mOffscreenPageLimit 默认值为1,即使设置了0(小于1)的值了,但是还会按照 mOffscreenPageLimit=limit=1 处理。
private int mOffscreenPageLimit = 1;//即使不设置,默认值就为1 public int getOffscreenPageLimit() { return this.mOffscreenPageLimit; } public void setOffscreenPageLimit(int limit) { if (limit < 1) {//设置为0,还是会默认为1 Log.w("ViewPager", "Requested offscreen page limit " + limit + " too small; defaulting to " + 1); limit = 1; } if (limit != this.mOffscreenPageLimit) { this.mOffscreenPageLimit = limit; this.populate(); }
1.2 解决思路
Fragment 有一个非生命周期的 setUserVisibleHint(boolean isVisibleToUser) 回调方法, 当 ViewPager 嵌套 Fragment 时会起作用 ,如果切换 ViewPager 则该方法也会被调用,参数 isVisibleToUser 为 true 代表当前 Fragment 对用户可见,否则不可见。 所以最简单的思路: Fragment 可见时才去加载数据,不可见时就不让它加载数据 。据我们创建抽象 BaseFragment ,对其进行封装。首先我们引入 isVisibleToUser 变量,负责保存当前 Fragment 对用户的可见状态。 同时还有几个值得注意的地方:
setUserVisibleHint(boolean isVisibleToUser) 方法的回调时机并没有与 Fragment 的生命周期有确切的关联,比如说,回调时机有可能在 onCreateView() 方法之后,也可能在 onCreateView() 方法之前。因此,必须引入一个标志位 isPrepareView 判断view是否创建完成,不然,很容易会造成空指针异常。我们初始化该变量为 false ,在 onViewCreated() 中,也就是view创建完成后,将其赋值为 true 。
数据初始化只应该加载一次,因此,引入第二个标志位, isInitData ,初始为 false, 在数据加载完成之后,将其赋值为 true ,下次返回此页面时不会再自动加载。至此,我们的懒加载方法考虑了所有条件。也就是当 isVisibleToUser 为 true , isInitData 为 false , isPrepareView 为 true 时,进行数据加载,并且加载后为了防止重复调用,将 isInitData 赋值为 true 。
将懒加载数据提取成一个方法,那么这个方法该何时调用呢?首先 setUserVisibleHint(boolean isVisibleToUser) 方法中是必须调用的,即当 Fragment 由可见变为不可见和不可见变为可见时回调。 其次,很容易忽略的一点。对于第一个 Fragment ,如果 setUserVisibleHint(boolean isVisibleToUser ) 方法在 onCreateView() 之前调用的话,如果懒加载方法只在 setUserVisibleHint(boolean isVisibleToUser ) 中调用,那么该 Fragment 将只能在被主动切换一次之后才能加载数据,这肯定是不可能的,因此,我们需要在view创建完成之后,也进行一次调用。思来想去,在 onActivityCreated() 方法中是最合适的。我们在继承的时候,在 onViewCreated() 方法中进行一些初始化就行了,这样不会引起冲突。
1.3 BaseFragment代码实现
public abstract class BaseFragment extends Fragment { private Boolean isInitData = false; //标志位,判断数据是否初始化 private Boolean isVisibleToUser = false; //标志位,判断fragment是否可见 private Boolean isPrepareView = false; //标志位,判断view已经加载完成 避免空指针操作 @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(getLayoutId(),container,false); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); isPrepareView=true;//此时view已经加载完成,设置其为true } /** * 懒加载方法 */ public void lazyInitData(){ if(!isInitData && isVisibleToUser && isPrepareView){//如果数据还没有被加载过,并且fragment已经可见,view已经加载完成 initData();//加载数据 isInitData=true;//是否已经加载数据标志重新赋值为true } } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); this.isVisibleToUser=isVisibleToUser;//将fragment是否可见值赋给标志isVisibleToUser lazyInitData();//懒加载 } /** * fragment生命周期中onViewCreated之后的方法 在这里调用一次懒加载 避免第一次可见不加载数据 * @param savedInstanceState */ @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); lazyInitData();//懒加载 } /** * 由子类实现 * @return 返回子类的布局id */ abstract int getLayoutId(); /** * 加载数据的方法,由子类实现 */ abstract void initData(); }
2.Fragment+ViewPager+Fragment情况
2.1 遇到的问题
如图2,对于这种由 Fragmentmanager 管理主页面的多个 Fragment 的显示与隐藏,在其中的某个 Fragment 中又嵌套了多个 Fragment 的情况( 如上图 ),上面的方案是无法解决的,如果主页面的 Fragment 直接继承上面的 BaseFragment ,就会出现主页的几个 Fragment 都不会加载的现象,为什么会这样呢,按道理说 Fragment 应该可见了,加载数据的判断逻辑应该没问题啊,而且上面那个demo也跑成功了。最终我发现,问题出在 setUserVisibleHint() 这个方法上,点进去它的源码发现注释中有这么一句话:
This may be used by the system to prioritize operations such as fragment lifecycle updates or loader ordering behavior.
也就是说这个可能被用来在一组有序的 Fragment 里 ,例如 Fragment 生命周期的更新。告诉我们这个方法被调用希望在一个pager里 ,因此 FragmentPagerAdapter 所以可以使用这个,而主页面的几个 Fragment 我们是通过 Fragmentmanager 管理的,所以 setUserVisibleHint() 是不会被调用,而我们设置的 isVisibleToUser=false 默认值一直不会变,那么 lazyInitData() 方法也就一直不会执行。
/** * 懒加载方法 */ public void lazyInitData(){ if(!isInitData && isVisibleToUser && isPrepareView){//因为isVisibleToUser一直都是false,所以iniData()是不会被执行的 initData();//加载数据 isInitData=true; } }
2.2 解决思路
这里我的处理方式是,在lazyInitData()中多加了一段处理逻辑,如下:
/** * 懒加载方法 */ public void lazyInitData(){ if(!isInitData && isVisibleToUser && isPrepareView){//如果数据还没有被加载过,并且fragment已经可见,view已经加载完成 initData();//加载数据 isInitData=true;//是否已经加载数据标志重新赋值为true }else if (!isInitData && getParentFragment()==null && isPrepareView){ initData(); isInitData=true; } } /** * Fragment显示隐藏监听 * @param hidden */ @Override public void onHiddenChanged(boolean hidden) { super.onHiddenChanged(hidden); if (!hidden) { lazyInitData(); } }
对于主页面的多个 Fragment 只会在第二个判断逻辑处理(因为它的 isVisibleToUser 值一直等于 false ),对于嵌套的 Fragment 只会经过第一个处理逻辑(因为它的 getParentFragment()!=null ),然后通过 onHiddenChanged() 方法去加载 lazyInitData() 方法,这样以来就能处理这种情况了。
但是这时候又会出现一个问题,如果一个APP里第一种,第二种情况并存的话,这段代码又不适合第一种情况了,因为对于第一种的情况当判定 isVisibleToUser 为 false 时,虽然不走第一个处理逻辑,但是它的 getParentFragment() 一直是等于 null 的,那么它就会走第二个判断逻辑,这样又会预加载了。
对于这种情况,我的处理方式:给每个Fragment设置一个标志值,当是第一种情况时,设为true,第二种情况时,设置false,然后再分别处理相应的判断逻辑。代码如下:
/** * 懒加载方法 */ public void lazyInitData(){ if(setFragmentTarget()){ if(!isInitData && isVisibleToUser && isPrepareView){//如果数据还没有被加载过,并且fragment已经可见,view已经加载完成 initData();//加载数据 isInitData=true;//是否已经加载数据标志重新赋值为true } }else { if(!isInitData && isVisibleToUser && isPrepareView){//如果数据还没有被加载过,并且fragment已经可见,view已经加载完成 initData();//加载数据 isInitData=true;//是否已经加载数据标志重新赋值为true }else if (!isInitData && getParentFragment()==null && isPrepareView ){ initData(); isInitData=true; } } } /** * 设置Fragment target,由子类实现 */ abstract boolean setFragmentTarget();
经过这样的处理之后,第一种情况和第二种情况,或两者并存的情况下都能保证在继承一个base下,实现懒加载。
2.3 BaseFragmentTwo最终代码实现
public abstract class BaseFragmentTwo extends Fragment { private Boolean isInitData = false; //标志位,判断数据是否初始化 private Boolean isVisibleToUser = false; //标志位,判断fragment是否可见 private Boolean isPrepareView = false; //标志位,判断view已经加载完成 避免空指针操作 @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(getLayoutId(),container,false); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); isPrepareView=true;//此时view已经加载完成,设置其为true } /** * 懒加载方法 */ public void lazyInitData(){ if(setFragmentTarget()){ if(!isInitData && isVisibleToUser && isPrepareView){//如果数据还没有被加载过,并且fragment已经可见,view已经加载完成 initData();//加载数据 isInitData=true;//是否已经加载数据标志重新赋值为true } }else { if(!isInitData && isVisibleToUser && isPrepareView){//如果数据还没有被加载过,并且fragment已经可见,view已经加载完成 initData();//加载数据 isInitData=true;//是否已经加载数据标志重新赋值为true }else if (!isInitData && getParentFragment()==null && isPrepareView ){ initData(); isInitData=true; } } } @Override public void onHiddenChanged(boolean hidden) { super.onHiddenChanged(hidden); if (!hidden) { lazyInitData(); } } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); this.isVisibleToUser=isVisibleToUser;//将fragment是否可见值赋给标志isVisibleToUser lazyInitData();//加载懒加载 } /** * fragment生命周期中onViewCreated之后的方法 在这里调用一次懒加载 避免第一次可见不加载数据 * @param savedInstanceState */ @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); lazyInitData(); } /** * 由子类实现 * @return 返回子类的布局id */ abstract int getLayoutId(); /** * 加载数据的方法,由子类实现 */ abstract void initData(); /** * 设置Fragment target,由子类实现 */ abstract boolean setFragmentTarget(); }
其它需要注意:
①给 viewpager 设置 adapter 时,一定要传入 getChildFragmentManager() ,否则 getParentFragment() 将会一直等于 null ,这会影响 lazyInitData() 的判断,导致懒加载出现混乱甚至无效的情况。
②demo中我使用的是 ViewPager+Tablayout 的组合方式,在使用 Tablayout 时一定要保证 styles.xml 中的主题应该使用 Theme.AppCompat.Light.NoActionBar 或者 Theme.AppCompat.Light 等 Theme.AppCompat.XXX 的主题。
项目地址
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍android实现ViewPager懒加载的三种方法,包括了android实现ViewPager懒加载的三种方法的使用技巧和注意事项,需要的朋友参考一下 在项目中ViewPager和Fragment接口框架已经是处处可见,但是在使用中,我们肯定不希望用户在当前页面时就在前后页面的数据,加入数据量很大,而用户又不愿意左右滑动浏览,那么这时候ViewPager中本来充满善意的预加载就有点
本文向大家介绍PHP实现懒加载的方法,包括了PHP实现懒加载的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了PHP实现懒加载的方法。分享给大家供大家参考。具体分析如下: 寻常php的加载是通过include(),require()等方法来加载外部文件,之后再通过实例调用方法或直接调用静态方法,而这样子写引入语句实在很麻烦,有的框架会将特定路径的文件全部引入,直接实例化就能使用,但这样
本文向大家介绍vue实现路由懒加载及组件懒加载的方式,包括了vue实现路由懒加载及组件懒加载的方式的使用技巧和注意事项,需要的朋友参考一下 一、为什么要使用路由懒加载 为给客户更好的客户体验,首屏组件加载速度更快一些,解决白屏问题。 二、定义 懒加载简单来说就是延迟加载或按需加载,即在需要的时候的时候进行加载。 三、使用 常用的懒加载方式有两种:即使用vue异步组件 和 ES中的imp
本文向大家介绍vue-router路由懒加载及实现的3种方式,包括了vue-router路由懒加载及实现的3种方式的使用技巧和注意事项,需要的朋友参考一下 什么是路由懒加载? 也叫延迟加载,即在需要的时候进行加载,随用随载。 官方解释: 1:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。 2:如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载
本文向大家介绍Android仿今日头条多个fragment懒加载的实现,包括了Android仿今日头条多个fragment懒加载的实现的使用技巧和注意事项,需要的朋友参考一下 前言 最近有时间,所以我又双叒叕推新一篇文章了,fragment懒加载实现虽然是个小模块,但做过的人都有体会,通常并不会轻易就成功了的,让你辗转反侧,彻夜难眠,绵绵无绝期。我就按照今日头条的样式做了一个懒加载功能。文章到一半
本文向大家介绍Android中Listview下拉刷新和上拉加载更多的多种实现方案,包括了Android中Listview下拉刷新和上拉加载更多的多种实现方案的使用技巧和注意事项,需要的朋友参考一下 listview经常结合下来刷新和上拉加载更多使用,本文总结了三种常用到的方案分别作出说明。 方案一:添加头布局和脚布局 android系统为listview提供了addf