聊聊RecyclerView缓存机制

简介: 聊聊RecyclerView缓存机制

1. 引言



网上有很多关于RecyclerView缓存的文章,那么为什么还要写这篇文章?写本文之前我也浏览了一些网上点击量比较高的文章,总体写的还不错,美中不足的是有的知识点,他们未必理解明白,有的用错误的结论表述,有的则一笔带过。为了让读者更快速的决定要不要观看此文,提出如下几个问题,如果你能给出正确答案,那么此文的知识点基本都掌握了。


  1. mAttachedScrap是干嘛的?这级缓存跟开发者的关系大吗?


  1. mChangedScrap又是干嘛的?跟开发者的关系大吗?


  1. 在一级缓存的维度上,为什么要同时设计mAttachedScrap和mChangedScrap两个不同的缓存?


  1. mUnmodifiableAttachedScrap的设计小技巧是什么?


  1. mCachedViews的缓存个数,以及该缓存中的ViewHolder有什么特性?


  1. mViewCacheExtension虽说是给开发者定制缓存策略的,但并没什么软用。


  1. RecyclerPool可以给多个RecyclerView共享缓存对象,但是如果设置不当,也会造成严重的性能问题?


  1. hasStableIds返回true,到底有啥用?


  1. onBindViewHolder(VH holder, int position, List payloads)这个方法到底该如何使用才好?2. 关于RecyclerView相关的文章


我之前写过一些关于RecyclerView的文章,阅读它们,有利于将RecyclerView的知识点串联起来,触类旁通。


1. RecyclerView滑动时干什么了
2. 详解RecyclerView动画原理之一
3. 详解RecyclerView动画原理之二


在RecyclerView滑动时干什么了一文中,讲解了RecyclerView滑动过程中复用和回收的先后顺序问题,但是并没有详细讲解RecyclerView的缓存机制,本文可作为该文的后续补充。


在详解RecyclerView动画原理系列文章中,接触到了mAttachedScrap,在LayoutManager的onLayoutChildren方法中,会先把RecyclerView上的View剥离开,放入到mAttachedScrap中,该文涉及到了缓存机制的一级缓存,阅读该文可以很好的了解mAttachedScrap是干嘛用的。


3. RecyclerView缓存架构图


RecyclerView的缓存,一图以蔽之。

640.png

由图可知,RecyclerView缓存是一个四级缓存的架构。当然,从RecyclerView的代码注释来看,官方认为只有三级缓存,即mCachedViews是一级缓存,mViewCacheExtension是二级缓存,mRecyclerPool是三级缓存。从开发者的角度来看,mAttachedScrap和mChangedScrap对开发者是不透明的,官方并未暴露出任何可以改变他们行为的方法。


3.1 mCacheViews可以通过如下方法,改变缓存的大小


public void setItemViewCacheSize(int size) {
    mRecycler.setViewCacheSize(size);
}


3.2 ViewCacheExtension则是一个抽象类,你完全可以自定义一个子类,修改获取缓存的策略。但是这个类只提供了获取缓存的接口,没有提供保存缓存的接口,对开发者要求甚高,而且使用RecyclerPool都能很好的实现一般的缓存需求。所以该接口,基本就是设计者的鸡肋,没啥软用。



public abstract static class ViewCacheExtension {
  public abstract View getViewForPositionAndType(Recycler recycler, int position,
          int type);
  }

3.3 RecyclerViewPool类提供了修改不同类型View的最大缓存数量,这对开发者很透明


public void setMaxRecycledViews(int viewType, int max) {
    ScrapData scrapData = getScrapDataForType(viewType);
    scrapData.mMaxScrap = max;
    final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
    while (scrapHeap.size() > max) {
        scrapHeap.remove(scrapHeap.size() - 1);
    }

4. RecyclerView$Recycler源码


我们知道RecyclerView的缓存功能是定义在RecyclerView$Recycler中的。

  public final class Recycler {
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        ArrayList<ViewHolder> mChangedScrap = null;
        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
        private final List<ViewHolder>
                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
        private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
        int mViewCacheMax = DEFAULT_CACHE_SIZE;
        RecycledViewPool mRecyclerPool;
        private ViewCacheExtension mViewCacheExtension;
        static final int DEFAULT_CACHE_SIZE = 2;
} 

我们来依次讲解不同层级的缓存:


4.1 mAttachedScrap


mAttachedScrap的对应数据结构是ArrayList,在LayoutManager#onLayoutChildren方法中,对views进行布局时,会将RecyclerView上的Views全部暂存到该集合中,以备后续使用,该缓存中的ViewHolder的特性是,如果和RV上的position或者itemId匹配上了,那么认为是干净的ViewHolder,是可以直接拿出来使用的,无需调用onBindViewHolder方法。该ArrayList的大小是没有限制的,屏幕上有多少个View,就会创建多大的集合。触发该层级缓存的场景一般是调用notifyItemXXX方法。调用notifyDataSetChanged方法,只有当Adapter hasStableIds返回true,会触发该层级的缓存使用。


4.2 mChangedScrap


mChangedScrap和mAttachedScrap是同一级的缓存,他们是平等的。但是mChangedScrap的调用场景是notifyItemChanged和notifyItemRangeChanged,只有发生变化的ViewHolder才会放入到mChangedScrap中。mChangedScrap缓存中的ViewHolder是需要调用onBindViewHolder方法重新绑定数据的。那么此时就有个问题了,为什么同一级别的缓存需要设计两个不同的缓存?有何作用,阅读过动画原理系列文章详解RecyclerView动画原理之一详解RecyclerView动画原理之二的同学会记得,在dispatchLayoutStep2阶段LayoutManager onLayoutChildren方法中最终会调用layoutForPredictiveAnimations方法,把mAttachedScrap中剩余的ViewHolder填充到屏幕上,所以他们的区别就是,mChangedScrap中的ViewHolder在RV填充满的情况下,是不会强行填充到RV上的。那么有办法可以让发生改变的ViewHolder进入mAttachedScrap缓存吗?当然可以。调用notifyItemChanged(int position, Object payload)方法可以,实现局部刷新功能,payload不为空,那么发生改变的ViewHolder是会被分离到mAttachedScrap中的。


4.3 mUnmodifiableAttachedScrap


mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap)是对mAttachedScrap的封装,它将mAttachedScrap暴露给开发者调用,它的特性就是只可读不能写。


4.4 mCachedViews


mCachedViews对应的数据结构也是ArrayList但是该缓存对集合的大小是有限制的,默认是2。该缓存中ViewHolder的特性和mAttachedScrap中的特性是一样的,只要position或者itemId对应上了,那么它就是干净的,无需重新绑定数据。开发者可以调用setItemViewCacheSize(size)方法来改变缓存的大小。该层级缓存触发的一个常见的场景是滑动RV。当然notifyXXX也会触发该缓存。该缓存和mAttachedScrap一样特别高效。


4.5 ViewCacheExtension


ViewCacheExtension开发者自己实现的意义不大,基本上所有你想做的,都可以通过RecyclerViewPool来实现。


4.6 RecyclerViewPool


RecyclerViewPool缓存可以针对多ItemType,设置缓存大小。默认每个ItemType的缓存个数是5。而且该缓存可以给多个RecyclerView共享。由于默认缓存个数为5,假设某个新闻App,每屏幕可以展示10条新闻,那么必然会导致缓存命中失败,频繁导致创建ViewHolder影响性能。所以需要扩大缓存size。


5. 结束


本文没有涉及到代码的讲解。一来网上的资料太多了,而且讲解的比较全,二来本文侧重点在于讲解各级缓存的作用和区别。关于开头的灵魂几问,如果您还不确定,赶紧翻开源码好好阅读一番吧。

相关文章
|
2月前
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
3月前
|
缓存 应用服务中间件 nginx
Web服务器的缓存机制与内容分发网络(CDN)
【8月更文第28天】随着互联网应用的发展,用户对网站响应速度的要求越来越高。为了提升用户体验,Web服务器通常会采用多种技术手段来优化页面加载速度,其中最重要的两种技术就是缓存机制和内容分发网络(CDN)。本文将深入探讨这两种技术的工作原理及其实现方法,并通过具体的代码示例加以说明。
302 1
|
1月前
|
存储 缓存 负载均衡
Nginx代理缓存机制
【10月更文挑战第2天】
63 4
|
1月前
|
存储 缓存 NoSQL
深入理解后端缓存机制的重要性与实践
本文将探讨在后端开发中缓存机制的应用及其重要性。缓存,作为提高系统性能和用户体验的关键技术,对于后端开发来说至关重要。通过减少数据库访问次数和缩短响应时间,缓存可以显著提升应用程序的性能。本文将从缓存的基本概念入手,介绍常见的缓存策略和实现方式,并通过实例展示如何在后端开发中有效应用缓存技术。最后,我们将讨论缓存带来的一些挑战及其解决方案,帮助您在实际项目中更好地利用缓存机制。
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
65 8
|
2月前
|
缓存 Java Python
python垃圾回收&缓存机制
python垃圾回收&缓存机制
|
3月前
|
存储 缓存 JavaScript
深入理解后端开发中的缓存机制
【8月更文挑战第31天】本文将通过一个实际的后端开发案例,介绍如何有效地使用缓存来提高应用性能。我们将从基础概念开始,逐步深入到缓存策略的实施,最后通过代码示例展示如何在Node.js环境中实现一个简单的缓存系统。无论你是缓存新手还是希望优化现有系统的开发者,这篇文章都将为你提供实用的指导和启示。
|
3月前
|
存储 缓存 关系型数据库
Django后端架构开发:缓存机制,接口缓存、文件缓存、数据库缓存与Memcached缓存
Django后端架构开发:缓存机制,接口缓存、文件缓存、数据库缓存与Memcached缓存
63 0
|
4月前
|
存储 缓存 NoSQL
解析Java中的缓存机制及其实现方式
解析Java中的缓存机制及其实现方式