详细聊聊RecyclerView缓存机制

简介: 详细聊聊RecyclerView缓存机制

前言


本文主要通过如下几个方面讲解RecyclerView缓存机制:

  1. ViewHolder
  2. 缓存架构
  3. Recycler.recycleViewHolderInternal(ViewHolder holder)
  4. Recycler.tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)

1. ViewHolder


1.1 作用

ViewHolder是对RecyclerView上的ItemView的封装,它是RecyclerView缓存的载体。它封装了以下属性:

  1. View itemView:对应RecyclerView的子View
  2. int mPosition:View当前对应数据在数据源中的位置
  3. int mOldPosition:View上次绑定的数据在数据源中的位置
  4. long mItemId:可以判断ViewHolder是否需要重新绑定数据
  5. int mItemViewType:itemView对应的类型
  6. int mPreLayoutPosition:在预布局阶段ViewHolder对应数据在数据源中的位置
  7. int mFlags:ViewHolder对应的标记位
  8. List<Ojbect> mPayloads:实现局部刷新
  9. Recycler mScrapContainer:如果不为空,表示ViewHolder是存放在Scrap缓存中

1.2 flag

  1. FLAG_BOUND:ViewHolder对应的View已经绑定好了数据,无需重新绑定
  2. FLAG_UPDATE:数据发生了变化,View需要重新绑定
  3. FLAG_INVALID:数据失效了,View需要重新绑定
  4. FLAG_REMOVED:数据从数据源中删除,View在消失动画中仍然有用
  5. FLAG_NOT_RECYCLABLE:ViewHolder不能被回收,ViewHolder对应ItemView做动画时需要保证ViewHolder不能被回收掉
  6. FLAG_RETURNED_FROM_SCRAP:从scrap缓存中获取到的ViewHolder
  7. FLAG_IGNORE:如果回收该类型的ViewHolder会报错
  8. FLAG_TMP_DETACHED:表示ItemView从RecyclerView上DETACHED了,detach和remove的区别是,remove会将View从ViewGroup的children数组中删除并且刷新ViewGroup,detach只会删除不会触发刷新
  9. FLAG_ADAPTER_FULLUPDATE:表示ViewHolder需要全量更新,如果没有设置该标志位,则是局部更新
  10. FLAG_MOVED:当ViewHolder的位置发生变化,做动画时需要使用
  11. FLAG_APPEARED_IN_PRE_LAYOUT:ViewHolder出现在预布局中,需要做APPEARED动画

2. 缓存架构


2.1 四级缓存

  1. ArrayList<ViewHolder> mAttachedScrap & ArrayListmChangedScrap
  2. ArrayList<ViewHolder> mCachedViews
  3. ViewCacheExtension mViewCacheExtension
  4. RecycledViewPool mRecyclerPool

640.png

640.png


2.2 scrap缓存

scrap缓存由mAttachedScrap和mChangedScrap两个缓存组成,在RecyclerView调用dispatchLayout时会使用该缓存,保存RecyclerView上的子View。

两部分组成
  1. mAttachedScrap
  2. mChangedScrap
缓存特性
  1. 对应的数据结构是ArrayList<ViewHolder>
  2. 缓存大小没有限制,大小等于RecyclerView子View的个数
  3. 该缓存中的ViewHolder无需重新绑定,只要ViewHolder的position和数据源中的position对应上了
  4. 调用notifyItemRemoved、notifyItemMoved、notifyItemInserted方法,ViewHolder放入mAttachedScrap中
  5. 调用notifyItemChanged(int position, Object payload),如果payload!=null ViewHolder放入mAttachedScrap中,否则ViewHolder放入mChangedScrap中
  6. 调用notifyDataSetChanged()时,如果Adapter.hasStableIds返回true,ViewHolder放入mAttachedScrap中,否则会将ViewHolder回收到非scrap缓存中
  7. LinearLayoutManager.layoutForPredictiveAnimations()阶段,mAttachedScrap数组剩下的ViewHolder是被挤出屏幕的

2.3 mCachedViews缓存

缓存特性
  1. 对应的数据结构是ArrayList<ViewHolder>
  2. 缓存大小有限制,默认缓存大小为2,可以修改默认缓存大小。如果使用GridLayoutManager建议设置为列的个数
  3. 该缓存中的ViewHolder无需重新绑定,只要ViewHolder的position和数据源中的position和itemType对应上了
  4. 该缓存的特性是FIFO
  5. ViewHolder mFlag如果有FLAG_INVALID、FLAG_REMOVED、FLAG_UPDATE、FLAG_ADAPTER_POSITION_UNKNOWN之一,不会放入该缓存
  6. 当RecyclerView滑动时会将ViewHolder放入该缓存或者从该缓存获取ViewHolder

2.4 ViewCacheExtension

该接口只提供了get方法,没提供put方法。

2.5 RecyclerViewPool

缓存特性
  1. 对应的数据结构是SparseArray<ScrapData>,根据itemType将缓存分组,组的数据结构是ScrapData
  2. ScrapData对应的数据结构是ArrayList<ViewHolder>,每个itemType对应的ScrapData的缓存大小默认值是5,可以修改缓存大小
  3. 该缓存中的ViewHolder需要重新绑定数据
  4. 可以提供给多个RecyclerView共享

3. Recycler.recycleViewHolderInternal(ViewHolder holder)


3.1 调用时机

  1. 调用notifyItemRemoved(),ViewHolder动画结束后
  2. 被挤出屏幕的ViewHolder在动画结束后
  3. 滑出屏幕的ViewHolder需要回收掉
  4. 调用notifyDataSetChanged(),而且Adapter.hasStableIds()返回false
  5. 缓存中获取到的ViewHolder校验失败,需要回收
  6. 调用setAdapter()时

3.2 回收逻辑

3.2.1 回收流程图

640.png



3.2.2 源码分析


  1. 如果当前mCachedViews容量>=缓存最大数量,将存放最久的ViewHolder回收到RecyclerViewPool中,直到mCachedViews size=mViewCacheMax-1
  2. 将ViewHolder缓存到mCachedViews最后一个槽位上
  3. 如果缓存到mCachedViews失败,则缓存到RecyclerViewPool中

3.2.2.1 Recycler.recycleViewHolderInternal()

image.png

image.png


3.2.2.2 Recycler.recycleCachedViewAt(int cachedViewIndex)

640.png

3.2.2.3 RecyclerViewPool.putRecycledView(ViewHolder scrap)

640.png

4. Recycler.tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)


4.1 调用时机(以LinearLayoutManager为例)

dispatchLayoutStep1()、dispatchLayoutStep2()、滑动RecyclerView时会发生复用

  1. LinearLayoutManager.onLayoutChildren() -> LinearLayoutManager.fill() -> LinearLayoutManager.layoutChunk() -> LinearLayoutManager.LayoutState.next()-> RecyclerView.Recycler.getViewForPosition()
  2. LinearLayoutManager.layoutForPredictiveAnimations()-> LinearLayoutManager.fill() -> LinearLayoutManager.layoutChunk() -> LinearLayoutManager.LayoutState.next()-> RecyclerView.Recycler.getViewForPosition()
  3. LinearLayoutManager.scrollBy()-> LinearLayoutManager.fill() -> LinearLayoutManager.layoutChunk() -> LinearLayoutManager.LayoutState.next()-> RecyclerView.Recycler.getViewForPosition()

4.2 获取ViewHolder流程

640.png



4.2.1 Recycler.getChangedScrapViewForPosition(int position)


预布局过程中从mChangedScrap缓存中获取ViewHolder。获取逻辑如下:

  1. 线性遍历 mChangedScrap,position == ViewHolder.mPreLayoutPosition,返回该ViewHolder,否则走逻辑2
  2. Adapter.hasStableIds()返回false,返回null,否则走逻辑3
  3. 线性遍历 mChangedScrap,mAdapter.getItemId(offsetPosition) == holder.getItemId,返回该ViewHolder,否则走逻辑4
  4. 上述都没有获取到,返回null。

640.png


4.2.2 Recycler.getScrapOrHiddenOrCachedHolderForPosition(int position)


4.2.2.1 该方法从三个地方获取缓存,获取后还需要验证是否合法,如果验证失败会调用Recycler.recycleViewHolderInternal()
  1. mAttachedScrap
  2. hidden list中,ChildHelper通过位图算法,逻辑隐藏的ViewHolder
  3. mCachedViews
4.2.2.2 从mAttachedScrap中获取,必须满足以下所有条件:
  1. holder.getLayoutPosition() == position,即预布局 viewHolder.mPreLayoutPosition == position,布局中 viewHolder.mPosition == position
  2. !holder.isInvalid()
  3. 非预布局情况下,不可返回removed的ViewHolder
4.2.2.3 从hidden list中获取ViewHolder
  1. 调用调用ChildHelper.findHiddenNonRemovedView(position)获取View
  2. 调用ChildHelper.unhide(view),不隐藏View
  3. 调用mChildHelper.detachViewFromParent(layoutIndex),轻量删除View,从RecyclerView children数组中删除,但是不会重新绘制RecyclerView
  4. 放入scrap缓存中
4.2.2.4 从mCachedViews中获取ViewHolder
  1. !holder.isInvalid()
  2. holder.getLayoutPosition() == position
  3. 如果满足会将ViewHolder从mCachedViews中移除

640.png

4.2.3 Recycler.getScrapOrCachedViewForId(long id, int type, boolean dryRun)


  1. 首先从attachedScrap中查找,如果id和type都匹配上了,返回该ViewHolder,并且在非预布局情况下,将removed的设置成update
  2. 如果id匹配上了 type没匹配上,将该ViewHolder回收掉
  3. 其次从mCachedViews中查找,如果id和type都匹配上了,返回并移除掉该ViewHolder
  4. 如果id匹配上了 type没匹配上,将该ViewHolder回收掉

640.png


4.2.4 getRecycledViewPool().getRecycledView(int type)

根据viewType从SparseArray中获取相应的ViewHolder,该缓存中获取的ViewHolder需要重新绑定

640.png


4.2.5 Adapter.createViewHolder(RecyclerView recyclerView, int type)

640.png


4.2.6 Recycler.tryBindViewHolderByDeadline(RecyclerView recyclerView, int type)


  1. 调用Adapter.bindViewHolder()方法
  2. 将ViewHolder flag设置为FLAG_BOUND,清除FLAG_UPDATE、FLAG_INVALID、FLAG_ADAPTER_POSITION_UNKNOWN
  3. 重写onBindViewHolder(VH holder, int position, List<Object> payloads),通过payloads可以实现局部刷新

640.png


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