热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

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

这篇文章主要介绍了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 的主题。

项目地址

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


推荐阅读
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 本文讨论了在Spring 3.1中,数据源未能自动连接到@Configuration类的错误原因,并提供了解决方法。作者发现了错误的原因,并在代码中手动定义了PersistenceAnnotationBeanPostProcessor。作者删除了该定义后,问题得到解决。此外,作者还指出了默认的PersistenceAnnotationBeanPostProcessor的注册方式,并提供了自定义该bean定义的方法。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
author-avatar
phper
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有