ListView缓存原理剖析

单类型View缓存机制

这里写图片描述

  • 请看上图,字母前面的数字表示元素在屏幕上的position,字母代表着View的类型,例如1A就表示第一个位置并且类型为A的View
  • 当从状态一转变为状态2后,1A被滑出,5A被滑入,由于5A在被滑入之前缓存池中没有元素,所以5A将被创建,并且当1A滑出屏幕时将被投入到缓存池中,缓存池是一个ArrayList的数组private ArrayList<View>[] mScrapViews;,我用下图代替
    这里写图片描述

  • 当从状态2变为状态3时,缓存池的状态如下图所示,注意,这里的6A其实复用的是缓存池中存在的1A,6A滑入时发现缓存池中有和自己类型相同的View,则直接将1A从缓存池中取出,相似的,2A被滑出后将被缓存池回收。
    这里写图片描述

结论:有没有一种生产者和消费者的感觉,一只手从缓存池中拿缓存,相应的一只手把移除的View投入缓存池中,这样会进入一个良性循环中,既无论ListView需要展现多少数据,在内存中存在的View的数量是恒定的,一直为一个屏幕所能展现的所有View+1(缓存池中的View),注意这个前提是基于加载的都是同类型的情况。

多类型View缓存机制

  • 多类型View的缓存机制和上面相差不大,请看下图
    这里写图片描述

  • 说一下状态4和状态5,从状态3 -> 状态4时,缓存池缓存了离开屏幕的3C,并且滑入7A时系统发现缓存池冲存在A类型的View,所以7A直接复用了1A,当从状态4 -> 状态5时,由于4C滑出,自然而然被缓存,注意此时缓存池中以及有了两个C(这就是为什么缓存池使用的数据结构是ArrayList数组的原因,也符合生产者和消费者的规则)

缓存原理剖析

  • 不知道大家有没有这样的一个疑惑,负责缓存的mScrapViews数组的容量是谁来确定的?并且,同类型的View被缓存时投入了同一个ArrayList中又是如何实现的?
  • 当我们需要ListView支持多类型复用时,往往要覆盖这两个方法(以下是他们的默认实现),getViewTypeCount就决定了mScrapViews数组的长度,getItemViewType就决定了相同类型的View投放到哪个坐标下,这句话的意思就是相同类型的View需要返回相同的值,并且它的值必须是从0开始依次递增的。,因此,我们由它们俩的默认实现看出当我们使用同类型加载数据的ListView时,这两个方法我们不必去理会。
    public int getItemViewType(int position) {
        return 0;
    }

    public int getViewTypeCount() {
        return 1;
    }
  • ListView的缓存原理由它的父类AbsListView的内部类RecycleBin负责,我们接下来就依次研究其中的重要方法。

  • 以下是缓存池的初始化,传递的参数既是getViewTypeCount的返回值

        public void setViewTypeCount(int viewTypeCount) {
            if (viewTypeCount < 1) {
                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
            }
            //noinspection unchecked
            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
            for (int i = 0; i < viewTypeCount; i++) {
                scrapViews[i] = new ArrayList<View>();
            }
            mViewTypeCount = viewTypeCount;
            mCurrentScrap = scrapViews[0];
            mScrapViews = scrapViews;
        }
  • 这个函数非常重要,ListView中的元素第一次初始化都会调用此方法,通俗来讲它是给每个View分类编号,请重点关注这句代码lp.viewType = mAdapter.getItemViewType(position);,调用了我们会复写的getItemViewType(position)方法,所以说,让相同类型的View返回同一个值是需要我们自己去维护的
    private void setItemViewLayoutParams(View child, int position) {
        final ViewGroup.LayoutParams vlp = child.getLayoutParams();
        LayoutParams lp;
        if (vlp == null) {
            lp = (LayoutParams) generateDefaultLayoutParams();
        } else if (!checkLayoutParams(vlp)) {
            lp = (LayoutParams) generateLayoutParams(vlp);
        } else {
            lp = (LayoutParams) vlp;
        }

        if (mAdapterHasStableIds) {
            lp.itemId = mAdapter.getItemId(position);
        }
        lp.viewType = mAdapter.getItemViewType(position);
        if (lp != vlp) {
          child.setLayoutParams(lp);
        }
    }
  • 接下来就是ListView中最核心的方法,我将难点都用注释标注,应该很好理解
View obtainView(int position, boolean[] isScrap) {  
    isScrap[0] = false;  
    View scrapView; 
    // 根据position调用getItemViewType(position)方法可以获得View在缓存池的位置
    scrapView = mRecycler.getScrapView(position);  
    View child;  
    if (scrapView != null) {  
        // 如果不为null,我们就可以利用convertView进行复用操作
        child = mAdapter.getView(position, scrapView, this);  
        if (child != scrapView) {
            // 如果返回的View和我们从缓存池中拿出的View不同,则把它重新存进去
            mRecycler.addScrapView(scrapView);  
            if (mCacheColorHint != 0) {  
                child.setDrawingCacheBackgroundColor(mCacheColorHint);  
            }  
        } else {  
            isScrap[0] = true;  
            dispatchFinishTemporaryDetach(child);  
        }  
    } else {  
        // 当缓存池中没有时,传递convertView为null
        child = mAdapter.getView(position, null, this);  
        if (mCacheColorHint != 0) {  
            child.setDrawingCacheBackgroundColor(mCacheColorHint);  
        }  
    }  
    return child;  
} 
  • 以上就是ListView缓存原理的分析,大家可以进入源代码的世界享受巧妙编码带来的享受,加深自己的理解感触。

总结

  • ListView的缓存机制利用了生产者和消费者的原理,View滑出屏幕时把它缓存起来,当下一个View滑入时,如果能在缓存池中找到,则把它取出来(从缓存池中remove掉了)复用。

ListView缓存机制可以优化的地方

  • 用于缓存的每一个类型的ArrayList没有容量限制,有可能内存中会缓存了很多同类型的View,这样是很大的浪费,解决方案是可以设置最大缓存数量,比如当A类型的View超过10个时,直接干掉ArrayList首部的View或者不再向缓存池中加入此类型的View,直到此类型的View数量在缓存池中小于5
  • 当同个类型的View的多个实例(数量大于某个值)在缓存池中存在过长时间时(时间大于某个值),可以利用缓存过期原理,逐步remove掉arraylist中的数据,并动态的设置变换缓存时间,达到高效利用(比如当arraylist[0]中存在11个缓存的View时,并且时间已经过了第一次过期时间5min,那么将remove掉第11个元素,如果又过4分钟此arraylist[0]还没有元素被使用,remove掉第10个元素,依次类推,直到最后一分钟还未使用,直接removeAll)
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值