美文网首页ITBOXandroid开发技巧View
ViewPager+Fragment组合的预加载和懒加载

ViewPager+Fragment组合的预加载和懒加载

作者: Crocutax | 来源:发表于2017-02-12 15:20 被阅读15055次

本文来自 Crocutax 的博客 , 转载请注明出处 http://www.crocutax.com

预加载介绍

ViewPager+Fragment的搭配在日常开发中也比较常见,可用于切换展示不同类别的页面,我们日常所见的咨询、购物、金融、社交等类型的APP都有机会用到这种控件组合.

例如:

今日头条APP

ViewPager控件有个特有的预加载机制,即默认情况下当前页面左右两侧的1个页面会被加载,以方便用户滑动切换到相邻的界面时,可以更加顺畅的显示出来.

通过ViewPager的setOffscreenPageLimit(int limit)可以设置预加载页面数量,当前页面相邻的limit个页面会被预加载进内存.

效果如下:注意看Log输出

viewpager预加载2页

懒加载介绍

所谓的懒加载,其实也就是延迟加载,就是等到该页面的UI展示给用户时,再加载该页面的数据(从网络、数据库等),而不是依靠ViewPager预加载机制提前加载两三个,甚至更多页面的数据.这样可以提高所属Activity的初始化速度,也可以为用户节省流量.而这种懒加载的方式也已经/正在被诸多APP所采用.

但是通过ViewPager方法setOffscreenPageLimit(int limit)的源码可以发现,ViewPager通过一定的逻辑判断来确保至少会预加载左右两侧相邻的1个页面,也就是说无法通过简单的配置做到懒加载的效果.

ViewPager方法setOffscreenPageLimit(int limit) 相关源码

//默认的缓存页面数量(常量)
private static final int DEFAULT_OFFSCREEN_PAGES = 1;

//缓存页面数量(变量)
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;

public void setOffscreenPageLimit(int limit) {
    //当我们手动设置的limit数小于默认值1时,limit值会自动被赋值为默认值1(即DEFAULT_OFFSCREEN_PAGES)
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "+ DEFAULT_OFFSCREEN_PAGES);
        limit = DEFAULT_OFFSCREEN_PAGES;
    }
    
    if (limit != mOffscreenPageLimit) {
        //经过前面的拦截判断后,将limit的值设置给mOffscreenPageLimit,用于
        mOffscreenPageLimit = limit;
        populate();
    }
}

关于变量mOffscreenPageLimit到底是什么.可以从其get方法注释中略见端倪

/**
 * 返回空闲状态下的视图层级中,当前页面任何一侧保存的页面数量,默认是1
 * Returns the number of pages that will be retained to either side of the
 * current page in the view hierarchy in an idle state. Defaults to 1.
 *
 * @return How many pages will be kept offscreen on either side
 * @see #setOffscreenPageLimit(int)
 */
public int getOffscreenPageLimit() {
    return mOffscreenPageLimit;
}

至于mOffscreenPageLimit到底是怎么影响ViewPager控件预加载的,暂不追查,因为此次的目的并不是ViewPager运行原理分析.

如何做到懒加载

既然通过ViewPager无法达到我们想要的懒加载效果,那么就得从Fragment自身入手了.

Fragment为我们提供了一个方法setUserVisibleHint(boolean isVisibleToUser),其中的参数isVisibleToUser就是表示该Fragment的UI对于用户是否可见

Fragment的方法 setUserVisibleHint(boolean isVisibleToUser)

/**
 * Set a hint to the system about whether this fragment's UI is currently visible
 * to the user. This hint defaults to true and is persistent across fragment instance
 * state save and restore.
 *
 * <p>An app may set this to false to indicate that the fragment's UI is
 * scrolled out of visibility or is otherwise not directly visible to the user.
 * This may be used by the system to prioritize operations such as fragment lifecycle updates
 * or loader ordering behavior.</p>
 *
 * <p><strong>Note:</strong> This method may be called outside of the fragment lifecycle.
 * and thus has no ordering guarantees with regard to fragment lifecycle method calls.</p>
 *
 * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
 *                        false if it is not.
 */
public void setUserVisibleHint(boolean isVisibleToUser) {
    if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
            && mFragmentManager != null && isAdded()) {
        mFragmentManager.performPendingDeferredStart(this);
    }
    mUserVisibleHint = isVisibleToUser;
    mDeferStart = mState < STARTED && !isVisibleToUser;
}

大意就是通过此方法来设置Fragment的UI对用户是否可见,当该页面对用户可见/不可见时,系统都会回调此方法.

我们可以重写此方法,然后根据回调的isVisibleToUser参数来进行相关的逻辑判断,以达到懒加载的效果,比如如果isVisibleToUser==true的话表示当前Fragment对用户可见,此时再去加载页面数据.

由于ViewPager内会装载多个Fragment,而这种懒加载机制对于各个Fragment属于共同操作,因此适合将其抽取到BaseFragment中.

注意

setUserVisibleHint(boolean isVisibleToUser)方法会多次回调,而且可能会在onCreateView()方法执行完毕之前回调.如果isVisibleToUser==true,然后进行数据加载和控件数据填充,但是onCreateView()方法并未执行完毕,此时就会出现NullPointerException空指针异常.

基于以上原因,我们进行数据懒加载的时机需要满足两个条件

  1. onCreateView()方法执行完毕
  2. setUserVisibleHint(boolean isVisibleToUser)方法返回true

所以在BaseFragment中用两个布尔型标记来记录这两个条件的状态.只有同时满足了,才能加载数据

//Fragment的View加载完毕的标记
private boolean isViewCreated;

//Fragment对用户可见的标记
private boolean isUIVisible;

第一步,改变isViewCreated标记

onViewCreated()方法执行时,表明View已经加载完毕,此时改变isViewCreated标记为true,并调用lazyLoad()方法

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    isViewCreated = true;
    lazyLoad();
}

第二步,改变isUIVisible标记

setUserVisibleHint(boolean isVisibleToUser)回调为true时,改变isUIVisible标记为true,并调用lazyLoad()方法

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    //isVisibleToUser这个boolean值表示:该Fragment的UI 用户是否可见
    if (isVisibleToUser) {
        isUIVisible = true;
        lazyLoad();
    } else {
        isUIVisible = false;
    }
}

第三步: 在lazyLoad()方法中进行双重标记判断,通过后即可进行数据加载

private void lazyLoad() {
    //这里进行双重标记判断,是因为setUserVisibleHint会多次回调,并且会在onCreateView执行前回调,必须确保onCreateView加载完毕且页面可见,才加载数据
    if (isViewCreated && isUIVisible) {
        loadData();
        //数据加载完毕,恢复标记,防止重复加载
        isViewCreated = false;
        isUIVisible = false;

        printLog(mTextviewContent+"可见,加载数据");
    }
}

第四步:定义抽象方法loadData(),具体加载数据的工作,交给子类去完成

protected abstract void loadData();

注意: 数据加载完毕要恢复标记,防止数据重复加载

效果如下:

Fragment懒加载示例

Demo源码

Github源码

相关文章

  • ViewPager+Fragment组合的预加载和懒加载

    本文来自 Crocutax 的博客 , 转载请注明出处 http://www.crocutax.com 预加载介绍...

  • 碎片

    viewPager+fragment懒加载

  • web优化之懒加载和预加载

    懒加载和预加载是常用的web优化的手段。所以我们首先应该明白什么是懒加载和预加载。懒加载:懒加载也加延迟加载,延迟...

  • 预加载+懒加载,ViewPager+Fragment

    预期效果:用户不可见界面只初始化UI,UI可见时开始获取数据并更新UI。 预加载 ViewPager默认会缓存当前...

  • ViewPager+Fragment预加载和懒加载分析

    1 什么是fragment的预加载和懒加载? 预加载:viewpager显示当前fragment的时候,viewp...

  • Android - 懒加载

    今天我们来聊一聊ViewPager+Fragment的懒加载。 1.什么是懒加载,为什么要用懒加载? 如果我们的项...

  • 懒加载和预加载

    懒加载和预加载 1. 懒加载 1. 什么是懒加载? 懒加载也就是延迟加载 当访问一个页面的时候,先把img元素或是...

  • JS

    JS 懒加载,预加载 概念:懒加载也叫延迟加载:JS图片延迟加载,延迟加载图片或符合某些条件时才加载某些图片。预加...

  • 实现ViewPager+Fragment懒加载的另一个套路

    实现ViewPager+Fragment懒加载的另一个套路 前言 我们实现ViewPager+Fragment懒加...

  • 图片懒加载

    懒加载与预加载的基本概念。 懒加载也叫延迟加载:JS图片延迟加载 延迟加载图片或符合某些条件时才加载某些图片。 预...

网友评论

  • Jay_Lwp:博主思路很清晰
  • 1bb34a60af2b:楼主,请问下这个场景怎么弄
    viewpager中有5个fragment,其中4个需要懒加载,并且页面的数据也只加载一次
    但是还有1个页面需要每次切换过去的时候都重新加载一次数据,这个怎么弄
    海芋洋芋:重写下setUerVisbity(boolean isVisibleToUser)方法,如果isVisibleToUser为true,则执行刷新的方法,否则不执行
    快乐石头111:viewpager 来回切换 Fragment 至少 隐藏会走onPause- > onStop() 恢复无论如何也会走onStart,所以你可以根据生命周期来做处理
  • SniffsTheRose:拜读了,思想学习了。提两点建议吧.
    1. isViewCreated标记 是冗余的,可直接通过Fragment#getView()获得。
    2. if (isVisibleToUser) {
    isUIVisible = true;
    lazyLoad();
    } else {
    isUIVisible = false;
    }
    可修改为:
    isUIVisible = isVisibleToUser;
    if(isUIVisible ) {
    lazyLoad();
    }
    beita08:你好, 想请问一下, 如果不使用isViewCreated标记, 而采用Fragment#getView()获得的值是否为null来判断,怎么避免重复加载的问题?
  • 4ac91c2be8d2:作者,以前是不是做后台开发的? 用过Hibdernate? 懒加载是后台开发数据库持久化加载实体对象才有的懒加载概念, 瞎JB乱把概念到处用,你这个概念误导多少人你知道不?
    Android应用层面就没有懒加载这个概念,这个概念在这里用根本就不合适,纯属误导人;

    setUserVisibleHint这个方法顶多就是一个被动触发, 与主动触发请求的一个合理利用系统API而已; 别乱用懒加载的概念, 有些没有开发过后台的同学, 不明所以,看过你的文章就开始到处乱说懒加载;
    Condor_c33a:@川_4ca4 逼事儿多,我们这边都叫懒加载~有什么关系么,名字而已~
    1fe067fe5392:@川_4ca4 没素质哦,管他叫什么,哪有什么唯一标准,最起码解决了我们的问题。
    4ac91c2be8d2:当时有人拿懒加载跟我理论, TM说我不会用Fragment,搞得我一脸闷B; 顶多算是一个被动与主动触发请求的合理利用API的事情, TM拿懒加载来忽悠人;
  • ToBeAbetter:学习了楼主,demo写的很好
    蜗牛改变自己大雄:谢谢博主,终于解决了我的问题。谢谢!:smile:
    Crocutax:@lyl920411 :pray:
  • yongshengdev:学习了楼主。
    发现一个问题,我用了4个fragment,从第四页面跳第一页面,如果onCreateView方法中,不进行view(fragment页面内容)的判空处理,每走onCreateView方法都会 isViewCreated = true; lazyLoad();会出现多次加载情况,加上判空后,view若不为空,就不走 isViewCreated = true; lazyLoad();就不会重复加载了。
    0d74096e4240:@用我的双手New出你的世界 这个后续有解决么
    我的资讯圈:@Crocutax 我也遇到这个问题了
    Crocutax: @SilenceBurst ok,我再研究完善下
  • efe81c2fc5c4:最近在用这种方式,发现setUserVisibleHint这个方法,要等fragment出现接近一秒才执行,不知道什么原因,
    b4b05032c85b:不可能吧 没有代码不知道你怎么写的
  • b4b05032c85b:你的视频播放有BUG 直接崩掉
    Crocutax: @fafgagaga 视频播放?
  • 217cce305b3c:向楼主学习!
  • haegyeong:学习了

本文标题:ViewPager+Fragment组合的预加载和懒加载

本文链接:https://www.haomeiwen.com/subject/xcyaittx.html