聊聊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. 结束


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

相关文章
|
3月前
|
缓存 安全 Java
7张图带你轻松理解Java 线程安全,java缓存机制面试
7张图带你轻松理解Java 线程安全,java缓存机制面试
|
1月前
|
缓存 应用服务中间件 Apache
缓存代理服务器的实现机制和技术选型
缓存代理服务器是一种特殊的代理服务器,其主要功能是缓存从目标服务器(通常是Web服务器)获取的数据,并在客户端再次请求相同数据时直接提供缓存的数据。通过缓存代理服务器可以加快访问速度并减轻目标服务器的负载。
|
2月前
|
缓存 调度
Eureka的注册表拉取及多级缓存机制简析
Eureka的注册表拉取及多级缓存机制简析
25 2
|
3月前
|
域名解析 存储 缓存
【域名解析DNS专栏】DNS缓存机制详解:如何提升域名解析速度
【5月更文挑战第21天】本文探讨了DNS缓存机制的原理及优化方法。DNS缓存是存储已解析域名与IP地址的临时数据库,能减少网络延迟,减轻服务器负担并提升用户体验。优化策略包括增加缓存容量,设置合理过期时间,使用智能DNS服务及定期清理缓存。文中还提供了一个Python示例,展示如何通过缓存提升域名解析速度。
381 2
【域名解析DNS专栏】DNS缓存机制详解:如何提升域名解析速度
|
1月前
|
存储 缓存 NoSQL
解析Java中的缓存机制及其实现方式
解析Java中的缓存机制及其实现方式
|
1月前
|
存储 缓存 NoSQL
实现返利App中的数据缓存与预加载机制
实现返利App中的数据缓存与预加载机制
|
2月前
|
SQL 缓存 Java
Java框架之MyBatis 07-动态SQL-缓存机制-逆向工程-分页插件
Java框架之MyBatis 07-动态SQL-缓存机制-逆向工程-分页插件
|
3月前
|
存储 缓存 前端开发
http缓存机制
HTTP缓存机制通过缓存控制头、实体标签和最后修改时间头优化Web性能,减少网络请求。Cache-Control指令如`public`, `private`, `max-age`, `no-cache`, `no-store`管理缓存行为。ETag用于验证资源完整性,Last-Modified检查资源是否更新。前端可利用Web存储和服务工作者进行细粒度缓存控制。正确配置缓存关键在于适应应用场景和需求。
|
3月前
|
缓存 移动开发 JavaScript
WKWebView对网页和js,css,png等资源文件的缓存机制及如何刷新缓存
WKWebView对网页和js,css,png等资源文件的缓存机制及如何刷新缓存
86 1