Android在多种设计下实现懒加载机制的方法

前言

前段时间在自己的练习项目中想用到懒加载机制,查看了大多数资料只介绍了在 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 则该方法也会被调用,参数 isVisibleToUsertrue 代表当前 Fragment 对用户可见,否则不可见。 所以最简单的思路: Fragment 可见时才去加载数据,不可见时就不让它加载数据 。据我们创建抽象 BaseFragment ,对其进行封装。首先我们引入 isVisibleToUser 变量,负责保存当前 Fragment 对用户的可见状态。 同时还有几个值得注意的地方:

setUserVisibleHint(boolean isVisibleToUser) 方法的回调时机并没有与 Fragment 的生命周期有确切的关联,比如说,回调时机有可能在 onCreateView() 方法之后,也可能在 onCreateView() 方法之前。因此,必须引入一个标志位 isPrepareView 判断view是否创建完成,不然,很容易会造成空指针异常。我们初始化该变量为 false ,在 onViewCreated() 中,也就是view创建完成后,将其赋值为 true

数据初始化只应该加载一次,因此,引入第二个标志位, isInitData ,初始为 false, 在数据加载完成之后,将其赋值为 true ,下次返回此页面时不会再自动加载。至此,我们的懒加载方法考虑了所有条件。也就是当 isVisibleToUsertrueisInitDatafalseisPrepareViewtrue 时,进行数据加载,并且加载后为了防止重复调用,将 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里第一种,第二种情况并存的话,这段代码又不适合第一种情况了,因为对于第一种的情况当判定 isVisibleToUserfalse 时,虽然不走第一个处理逻辑,但是它的 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.LightTheme.AppCompat.XXX 的主题。

项目地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android 多层嵌套后的 Fragment 懒加载实现示例

    多层嵌套后的 Fragment 懒加载 印象中从 Feed 流应用流行开始,Fragment 懒加载变成了一个大家都需要关注的开发知识,关于 Fragment 的懒加载,网上有很多例子,GitHub 上也有很多例子,就连我自己在一年前也写过相关的文章.但是之前的应用可能最多的是一层 Activity + ViewPager 的 UI 层次,但是随着页面越来越复杂,越来越多的应用首页一个页面外层是一个 ViewPager 内部可能还嵌套着一层 ViewPager,这是之前的懒加载就可能不那么好用了

  • android实现ViewPager懒加载的三种方法

    在项目中ViewPager和Fragment接口框架已经是处处可见,但是在使用中,我们肯定不希望用户在当前页面时就在前后页面的数据,加入数据量很大,而用户又不愿意左右滑动浏览,那么这时候ViewPager中本来充满善意的预加载就有点令人不爽了.我们能做的就是屏蔽掉ViewPager的预加载机制.虽然ViewPager中提供的有setOffscreenPageLimit()来控制其预加载的数目,但是当设置为0后我们发现其根本没效果,这个的最小值就是1,也就是你只能最少前后各预加载一页.那么,这时候

  • Android之Viewpager+Fragment实现懒加载示例

    我们在做应用开发的时候,一个Activity里面可能会以viewpager(或其他容器)与多个Fragment来组合使用.而ViewPager默认会缓存三页数据,即:Viewpager每加载一个Fragment,都会预先加载此Fragment左侧或右侧的Fragment.而如果每个fragment都需要去加载数据,或从本地加载,或从网络加载,那么在这个activity刚创建的时候就变成需要初始化大量资源,浪费用户流量不止,还造成卡顿,这样的结果,我们当然不会满意.那么,能不能做到当切换到这个fr

  • 详解Android 在 ViewPager 中使用 Fragment 的懒加载

    我们先看一下效果: 首先,我们要知道什么是懒加载: 懒加载,就是先初始化控件,在用户可见的时候再加载数据. 为什么要懒加载? 懒加载多被使用在新闻资讯类客户端中,试想那么多的分类如果一下子都加载出来,真的是极大地消耗了系统资源.可能有人会说 ViewPager 有 viewPager.setOffscreenPageLimit() 的方法,我们传个 0 进去不就好了吗?看过源码的应该知道,即便你传了 0 进去,系统也会默认为 1 的,也就是 ViewPager 依然会加载当前页面的前后各一个 F

  • Android开发技巧之Fragment的懒加载

    前言 所谓懒加载,就是当fragment完全可见的时候我们再去加载数据,我们在做应用开发的时候,一个Activity里面可能会以viewpager(或其他容器)与多个Fragment来组合使用,而如果每个fragment都需要去加载数据,或从本地加载,或从网络加载,那么在这个activity刚创建的时候就变成需要初始化大量资源.这样的结果,我们当然不会满意.那么,能不能做到当切换到这个fragment的时候,它才去初始化呢? 答案就在Fragment里的setUserVisibleHint这个方

  • Android优化方案之Fragment的懒加载实现代码

    一.背景 在Android应用中,ViewPager是我们不可避免使用的一个控件,因为它可以使我们在占用较少空间的同时,增强内容的丰富性,同时以其内部流淌着Google的血液,所以它几乎成了每一个App的标配控件.但是,假如ViewPager的每一个Fragment都需要通过网络拉取数据加载,而ViewPager是默认加载前两项的,所以在很容易造成网络丢包或者网络堵塞等问题,所以Fragment使用懒加载是非常有必要的. 举个栗子: 如上图所示,我们有两个大的Tab:人物和风景.而人物Tab下有

  • Android界面数据懒加载实现代码

    大家在使用手机新闻客户端的时候就会有一个发现,大多数的新闻客户端都会把新闻分类,诸如头条.娱乐.体育.科技等等,如何实现这种界面的呢?这个实现起来其实很简单,就是在一个Fragment中实现多个ViewPage的切换,再在ViewPage的上面放一个TabLayout,关联起来就可以实现联动效果.如果大家感觉不太明了的话,以后我可以专门写一篇关于Fragment中放入多个ViewPage的博客,今天,我主要介绍的是怎样实现界面即Fragment的懒加载.那么,大家就会奇怪了既然是加载界面直接加载

  • Android仿今日头条多个fragment懒加载的实现

    前言 最近有时间,所以我又双叒叕推新一篇文章了,fragment懒加载实现虽然是个小模块,但做过的人都有体会,通常并不会轻易就成功了的,让你辗转反侧,彻夜难眠,绵绵无绝期.我就按照今日头条的样式做了一个懒加载功能.文章到一半会解释大家可能遇到的不加载数据了的坑,先不剧透. Fragment的生命周期回顾 github代码直通车 (本地下载) 这里是今日头条效果: 自制效果,有图有真相: 实现思路: 使用Fragment类自带方法setUserVisibleHint()判断当前fragment是否

  • Android在多种设计下实现懒加载机制的方法

    前言 前段时间在自己的练习项目中想用到懒加载机制,查看了大多数资料只介绍了在 View Pager + Fragment 组合的情况下实现的懒加载,但是现在大多数App更多的是 Fragmentmanager 去管理主页面多个 Fragment 的显示与隐藏,然后主界面的某个或多个 Fragment 里又嵌套了多个 Fragment + ViewPager (详细见下图 ),对于这种情况,适用于第一种的方式是不能直接解决第二种的情况的,所以写下这篇文章,记录一下踩的几个坑,希望对同像我一样的初学

  • vue elementUI table表格数据 滚动懒加载的实现方法

    在项目中遇到了一个性能问题 vue+elementUI table表格展示数据,当数据很多的时候,不能一页显示完,同时一次请求数据量太大,会增加网页渲染的时间,影响体验, 这个时候常常有两种方法处理, 1.分页,如下 2.如果我不想分页,又想在一页显示全部数据呢?这个时候其实就可以用数据懒加载了 如下一开始表格只显示31行数据 当将滚动条拉到低的时候,就会再加载31条数据,如果剩下的数据不足31,那就加载剩下的 根据项目需求,这需要一页可以看到全部数据,所以我选择了第二中方式 那么第二种方式要怎

  • SpringBoot新特性之全局懒加载机制

    关于延迟加载 在 Spring 中,默认情况下所有定的 bean 及其依赖项目都是在应用启动时创建容器上下文是被初始化的.测试代码如下: @Slf4j @Configuration public class DemoConfig { public DemoConfig() { log.warn(" > > > demoConfig 被初始化 > > >"); } } 启动应用日志: [ main] o.a.c.c.C.[Tomcat].[localh

  • Angular懒加载机制刷新后无法回退的快速解决方法

    今天在项目中遇到一个很奇怪的问题,使用oclazyload懒加载angular的模块,刷新页面后,单击回退按钮无法返回上一个页面.估计是使用懒加载机制销毁了angular内部的state关联,导致无法回到上一个state(单击回退按钮 ui-routre的 $stateChangeStart 事件都不会触发),当然这只是猜测,由于事件关系也没有去深入的探究源码. angular懒加载机制刷新后无法回退的解决方案 : 通过查看angular(ionic)的源码发现$browser这个服务上有个on

  • hibernate 中 fetch=FetchType.LAZY 懒加载失败处理方法

    对这种懒加载问题,最后的做法是利用Spring提供的一个针对Hibernate的一个支持类,其主要意思是在发起一个页面请求时打开Hibernate的Session,一直保持这个Session,使得Hibernate的Session的生命周期变长,直到这个请求结束,具体是通过一个Filter来实现的. 那么,如果现在我们想用Hibernate懒加载特性,又想用延长session的生命周期,知道将数据提到页面显示(经过action层),那么我们就得在web.xml文件中增加以下配置: <!-- 配置

  • PHP类的自动加载机制实现方法分析

    本文实例讲述了PHP类的自动加载机制实现方法.分享给大家供大家参考,具体如下: Test1.class.php <?php class Test1 { public static function test() { echo "hello,world!\n"; } } Test2.class.php <?php class Test2 { public static function test() { echo "你好,世界!\n"; } } test.

  • Javascript实现图片懒加载插件的方法

    前言 网络上各大论坛,尤其是一些图片类型的网站上,在图片加载时均采用了一种名为懒加载的方式,具体表现为,当页面被请求时,只加载可视区域的图片,其它部分的图片则不加载,只有这些图片出现在可视区域时才会动态加载这些图片,从而节约了网络带宽和提高了初次加载的速度,具体实现的技术并不复杂,下面分别对其说明. Web 图片的懒加载就是通过读取img元素,然后获得img元素的data-src(也可以约定为其他属性名)属性的值,并赋予img的src,从而实现动态加载图片的机制. 这里需要注意的是: img在初

随机推荐