iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >AndroidRecyclerView缓存复用原理解析
  • 870
分享到

AndroidRecyclerView缓存复用原理解析

AndroidRecyclerView缓存复用AndroidRecyclerView 2022-11-13 19:11:23 870人浏览 八月长安
摘要

目录一、牵出缓存1.缓存还在屏幕内的ViewHolder——Scrap缓存mAttachedScrapmChangeScrap用一个例子说明2.缓存屏幕之外的

一、牵出缓存

都有哪些缓存,作用是什么,为什么这么设计

1.缓存还在屏幕内的ViewHolder——Scrap缓存

Scrap是RecyclerView中最轻量的缓存,它不参与滑动时的回收复用,只是作为重新布局时的一种临时缓存,缓存(保存)动作只发生在重新布局时,布局完成后就要清空缓存。它的目的是,缓存当界面重新布局(不包括初始化第一次)的前后都出现在屏幕上的ViewHolder,这样就省去了不必要的CreateView和bindView的工作

mAttachedScrap

mAttachedScrap的数据结构

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();

它是用来保存将会原封不动的ViewHolder,例如调用notifyItemChanged方法时,在布局前先把那些屏幕内没有改变的ViewHolder保存在mAttachedScrap中,在布局时复用mAttachedScrap中的ViewHolder,布局结束后清空mAttachedScrap,缓存的动作只发生在布局前,复用的动作只发生在布局时,布局后清空

mChangeScrap

mChangeScrap的数据结构:

ArrayList<ViewHolder> mChangedScrap

它是用来保存位置会发生移动的ViewHolder,注意只是位置发生移动,内容仍旧是原封不动,例如Remove掉一个Item,在布局前就能知道屏幕内哪些View是原封不动的,这些原封不动的保存在mAttachedScrap中,哪些View是只变换位置的,这些只变换位置的保存在mChangeScrap中,在布局时不变的复用mAttachedScrap中的,只有位置变化的复用mChangeScrap中的,缓存的动作只发生在布局前,复用的动作只发生在布局时,布局后清空

用一个例子说明

上图描述的是我们在一个RecyclerView中删除B项,并且调用了notifyItemRemoved()时,mAttachedScrap与mChangedScrap分别会临时存储的View情况。此时,A是在删除前后完全没有变化的,它会被临时放入mAttachedScrap。B是我们要删除的,它也会被放进mAttachedScrap,但是会被额外标记REMOVED,并且在之后会被移除。C和D在删除B后会向上移动位置,因此他们会被临时放入mChangedScrap中。E在此次操作前并没有出现在屏幕中,它不属于Scrap需要管辖的,Scrap只会缓存屏幕上已经加载出来的ViewHolder。在删除时,A,B,C,D都会进入Scrap,而在删除后,A,C,D都会回来,其中C,D只进行了位置上的移动,其内容没有发生变化。

RecyclerView的局部刷新,依赖的就是Scrap的临时缓存,我们需要通过notifyItemRemoved()、notifyItemChanged()等系列方法通知RecyclerView哪些位置发生了变化,这样RecyclerView就能在处理这些变化的时候,使用Scrap来缓存其它内容没有发生变化的ViewHolder,于是就完成了局部刷新。需要注意的是,如果我们使用notifyDataSetChanged()方法来通知RecyclerView进行更新,其会标记所有屏幕上的View为FLAG_INVALID,从而不会尝试使用Scrap来缓存一会儿还会回来的ViewHolder,而是统统直接扔进RecycledViewPool池子里,回来的时候就要重新走一遍绑定的过程。

Scrap只是作为布局时的临时缓存,它和滑动时的缓存没有任何关系,它的detach和重新attach只临时存在于布局的过程中。布局结束时Scrap列表应该是空的,其成员要么被重新布局出来,要么将被移除,总之在布局过程结束的时候,两个Scrap列表中都不应该再存在任何东西。

2.缓存屏幕之外的ViewHolder——CacheView

CacheView是在RecyclerView列表位置产生变动的时候,对刚刚移出屏幕的View进行回收复用的缓存列表,它的数据结构是:

final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

mCacheViews的缓存动作发生在滑动时,当有Item滑出屏幕外,就会原封不动的保存到mCacheViews中,复用动作发生在滑动回来的时候,场景是当上下小距离滑动时,刚划出去的Item又划回来,不用再重新创建和重新绑定数据

注意mCachedViews是有大小限制的,默认最大是2,当超过2时会怎样呢?

            if (forceRecycle || holder.isRecyclable()) {
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                    //?mViewCacheMax的值是2
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }
                    int targetCacheIndex = cachedViewSize;
                    if (ALLOW_THREAD_GAP_WORK
                            && cachedViewSize > 0
                            && !mPrefetchReGIStry.lastPrefetchIncludedPosition(holder.mPosition)) {
                        // when adding the view, skip past most recently prefetched views
                        int cacheIndex = cachedViewSize - 1;
                        while (cacheIndex >= 0) {
                            int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                            if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                                break;
                            }
                            cacheIndex--;
                        }
                        targetCacheIndex = cacheIndex + 1;
                    }
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } 
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
    recycleCachedViewAt(0);
    cachedViewSize--;
}

当mCachedViews的长度大于等于2时,就会移除索引为0的ViewHolder,这第0个是最早缓存进来的,这体现了LRU缓存的特性,淘汰最近不常用的,然后因为是ArrayList所以索引是1的会替补到索引是0的位置,然后下面把新加进来的VIewHolder放到索引是1的位置。

3.mViewCacheExtension

这是Google工程师预留给程序员的,可以做自己的缓存逻辑。

4.RecycledViewPool

RecycledViewPool是最后一层缓存:

        RecycledViewPool getRecycledViewPool() {
            if (mRecyclerPool == null) {
                mRecyclerPool = new RecycledViewPool();
            }
            return mRecyclerPool;
        }
    public static class RecycledViewPool {
        static class ScrapData {
            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
            int mMaxScrap = DEFAULT_MAX_SCRAP;
            long mCreateRunningAverageNs = 0;
            long mBindRunningAverageNs = 0;
        }
        SparseArray<ScrapData> mScrap = new SparseArray<>();
    }

我们可以在RecyclerView中找到RecycledViewPool,可以看见它的保存形式是和上述的Srap、CacheView都不同的,它的数据结构是一个SparseArray,它的Value的数据类型是ScrapData,ScrapData中主要维护了一个ViewHolder的ArrayList。

原因是RecycledViewPool保存的是以ViewHolder的viewType为区分(我们在重写RecyclerView的onCreateViewHolder()时可以发现这里有个viewType参数,可以借助它来实现展示不同类型的列表项)的多个列表。

与前两者不同,RecycledViewPool在进行回收的时候,目标只是回收一个该viewType的ViewHolder对象,并没有保存下原来ViewHolder的内容,在保存之前会进行ViewHolder的格式化清空数据内容,因为清空后的ViewHolder都是一样的,所以它只保存前五个,后面的直接丢掉,并没有使用LRU缓存逻辑,在复用时,将会调用bindViewHolder()按照我们在onBindViewHolder()描述的绑定步骤进行重新绑定,从而摇身一变变成了一个新的列表项展示出来。

同样,RecycledViewPool也有一个最大数量限制,默认情况下是5。在没有超过最大数量限制的情况下,Recycler会尽量把将被废弃的ViewHolder回收到RecycledViewPool中,以期能被复用。值得一提的是,RecycledViewPool只会按照ViewType进行区分,只要ViewType是相同的,甚至可以在多个RecyclerView中进行通用的复用,只要为它们设置同一个RecycledViewPool就可以了。

总的来看,RecyclerView着重在两个场景使用缓存与回收复用进行了性能上的优化。一是,在数据更新时,利用Scrap实现局部更新,尽可能地减少没有被更改的View进行无用地重新创建与绑定工作。二是,在快速滑动的时候,重复利用已经滑过的ViewHolder对象,以尽可能减少重新创建ViewHolder对象时带来的压力。总体的思路就是:只要没有改变,就直接重用;只要能不创建或重新绑定,就尽可能地偷懒。

二、到底是四级缓存还是三级缓存

Google工程师告诉我们有三层缓存,分别是:CacheView、mViewCacheExtension、RecycledViewPool

其实我们发现还有一个:Scrap

到此这篇关于Android RecyclerView缓存复用原理解析的文章就介绍到这了,更多相关Android RecyclerView 内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: AndroidRecyclerView缓存复用原理解析

本文链接: https://www.lsjlt.com/news/170586.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • AndroidRecyclerView缓存复用原理解析
    目录一、牵出缓存1.缓存还在屏幕内的ViewHolder——Scrap缓存mAttachedScrapmChangeScrap用一个例子说明2.缓存屏幕之外的...
    99+
    2022-11-13
    Android RecyclerView缓存复用 Android RecyclerView
  • vuex store 缓存存储原理分析
    目录vuex store 缓存存储原理如何使用store缓存数据1 src/store/index.js 写方法2 src/store/getter.js3 使用 store 存取数...
    99+
    2024-04-02
  • MyBatis一级缓存与二级缓存原理与作用分析
    目录缓存的作用MyBatis 的缓存结构一级缓存二级缓存缓存的作用 在 Web 系统中,最重要的操作就是查询数据库中的数据。但是有些时候查询数据的频率非常高,这是很耗费数据库资源的,...
    99+
    2022-12-27
    Mybatis 一级缓存 Mybatis 二级缓存 MyBatis一级缓存和二级缓存
  • Mybatis的一级缓存和二级缓存原理分析与使用
    目录Mybatis的一级缓存和二级缓存1 Mybatis如何判断两次查询是完全相同的查询2 二级缓存2.1 二级缓存配置2.2 二级缓存特点2.3 配置二级缓存2.4 测试Mybat...
    99+
    2024-04-02
  • Java MyBatis本地缓存原理详解
    目录背景发现问题复现解决问题探究缓存的原理Sql查询部分深入初见缓存告一段落番外篇-Myabtis创建CacheKey的算法。构造方法结束语背景 出现了一次生产事故,事情是这样的,我...
    99+
    2024-04-02
  • docker 清理缓存脚本解析
    目录一、硬盘占用查看1.硬盘空间查看2.inode查看3.Docker占用的磁盘空间查看二.docker清理1.删除所有已经停止的容器2.删除所有未打标签的镜像3.删除所有无用的vo...
    99+
    2024-04-02
  • HTTP缓存原理介绍
    这篇文章主要讲解了“HTTP缓存原理介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“HTTP缓存原理介绍”吧!通过Internet获取资源既缓慢,成本又高...
    99+
    2024-04-02
  • redis缓存实现原理
    redis 缓存机制通过键值对存储、内存存储、过期策略、数据结构、复制和持久化来实现。它遵循获取数据、缓存命中、缓存不命中、写入缓存、更新缓存的步骤,提供快速的数据访问和高性能的缓存服务...
    99+
    2024-04-19
    redis 数据访问 键值对
  • SpringBoot 缓存 Caffeine使用解析
    目录Redis和Caffeine的区别相同点不同点联系Spring Boot 缓存 Caffeine使用1.需要添加的依赖2.配置3.使用Caffeine缓存Caffeine其他常用...
    99+
    2024-04-02
  • 详解vue computed的缓存实现原理
    目录初始化 computed依赖收集派发更新总结一下本文围绕下面这个例子,讲解一下computed初始化及更新时的流程,来看看计算属性是怎么实现的缓存,及依赖是怎么被收集的。 &...
    99+
    2024-04-02
  • 解析springboot整合谷歌开源缓存框架Guava Cache原理
    目录Guava Cache:⾕歌开源缓存框架Guava Cache使用使用压测⼯具Jmeter5.x进行接口压力测试:压测⼯具本地快速安装J...
    99+
    2024-04-02
  • redis缓存存储Session原理机制
    目录基于 Redis 存储 Session首先安装 redis 存储引擎的包设置session过期时间分布式获取Session:(redis)基于 Redis 存储 Session ...
    99+
    2024-04-02
  • Mybatis 一级缓存和二级缓存原理区别
    目录Mybatis缓存Mybatis一级缓存1.为什么需要Mybatis一级缓存2.Mybatis一级缓存的实现3.Mybatis一级缓存配置Mybatis二级缓存1.为什么需要My...
    99+
    2024-04-02
  • PHP中Memcache缓存原理及其对缓存数据的作用
    随着互联网的发展,数据量的快速增长,缓存技术的应用越来越广泛,它既可以提升网站的访问速度,也可以减轻服务器的负担。在服务器端,Memcache作为 PHP 缓存技术的一种,被广泛应用于大型网站的访问优化。本文将介绍 Memcache 缓存的...
    99+
    2023-05-15
    PHP Memcache缓存 缓存原理
  • Hibernate缓存机制的原理
    本篇内容主要讲解“Hibernate缓存机制的原理”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Hibernate缓存机制的原理”吧!1. 为什么要用 Hibe...
    99+
    2024-04-02
  • Redis缓存原理是什么
    这篇文章将为大家详细讲解有关Redis缓存原理是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1. Redis是什么Redis 是一个高性能的开源的、C语言写的Nos...
    99+
    2024-04-02
  • http缓存技术的原理
    本篇内容主要讲解“http缓存技术的原理”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“http缓存技术的原理”吧!HTTP缓存实现的原理浏览器是如何知道使用缓存的,其实这都是通过http中,浏览...
    99+
    2023-06-05
  • HTTP缓存机制的原理
    这篇文章主要介绍“HTTP缓存机制的原理”,在日常操作中,相信很多人在HTTP缓存机制的原理问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”HTTP缓存机制的原理”的疑惑有所帮...
    99+
    2024-04-02
  • ASP.NET缓存的工作原理
    这篇文章主要介绍“ASP.NET缓存的工作原理”,在日常操作中,相信很多人在ASP.NET缓存的工作原理问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”ASP.NET缓存的工作原理”的疑惑有所帮助!接下来,请跟...
    99+
    2023-06-17
  • redis缓存原理与实现
    redis 缓存是一种内存中键值对存储,通过将常用数据存储在内存中,提升应用程序性能。其实现原理包括哈希表、跳跃表、异步 i/o、内存映射、复制和持久化等技术,带来提高性能、减少延迟、提...
    99+
    2024-04-19
    redis 数据丢失 键值对
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作