带着问题学习

  1. RecyclerView 有几级缓存?分别是什么?
  1. RecyclerView 各级缓存的作用?
  1. RecyclerView 缓存间的关系?
  1. 为什么有的缓存取出来不用重新执行 onBindViewHolder(),而有的需要?

RecyclerView 的四级缓存

Google 官方不把 Scrap 算入缓存,但是,因为在加载 ViewHolder 时,有从 Scrap 中获取的步骤,所以,个人认为将其当成第一级缓存是比较合适的
  • ScrapmAttachedScrapmChangedScrap
  • CachedView
  • ViewCacheExtension
  • RecycledViewPool

Scrap

scrap 即作废、废弃的意思 用于缓存在屏幕上显示的 ViewHolder 无默认大小,屏幕上有多少个 ViewHolder,就有多大

什么时候会触发该层缓存机制?

  • 该层缓存机制应该是一直存在的,也就是,不管 scarp 中是否有缓存,每次调用 next() 方法时,都会优先从 scarp 层中尝试获取缓存进行复用。
  • 而在调用 LayoutManager.onLayoutChildren()notifyItemXXX() 方法时,会把 ViewHoldermAttachedScrapmChangedScrap 中存放。

为什么在屏幕上显示的 ViewHolder,也要被缓存,而且还是 被废弃 后缓存起来的呢?

  • 因为在 RecyclerView 显示的过程中,每次更新,都会把所有的 ViewHolder 都从屏幕上分离开,也就是,每次更新的时候,都会把正在显示的 View 都标记为 Scrap/废弃 状态
  • 既然每次都把所有的 ViewHolder 从屏幕上分离开,自然就要将其 缓存 起来,方便刷新后复用,因为两次相邻的刷新之间,ViewHolder 的相似度还是很高的,如果每次都重新走一遍 onCreateViewHolder()onBindViewHolder() 的话,那就太浪费资源了。

那为什么 Scrap 要有 attachedScrap()changedScarp() 两个不同的缓存列表呢?

  • 因为对于在 RecyclerView 列表中的每个 ViewHolder 来说,在刷新时,无非就是 能否直接复用 两种状态
  • 能直接复用的:放入 mAttachedScrap 中,后面直接复用
  • 不能直接复用的:放入 mChangedScrap 中,复用时需要调用 onBindViewHolder() 重新绑定数据

决定放入 mAttachedScrapmChangedScrap 的策略是什么?

  • mAttachedScrap:调用 notifyItemRemovednotifyItemMovednotifyItemInserted 之类的方法时,会把 ViewHolder放入 mAttachedScrap
  • mChangedScrap:调用 notifyItemChanged()notifyItemRangeChanged() 之类的方法时,会把 ViewHolder 放入 mChangedScrap
  • 直接看源码:

什么时候从 mAttachedScrap 中获取?

  • 当缓存起来的 ViewHodlerpositionRecyclerView 中要加载的 position 对应上的时候,就从 mAttachedScrap 中获取缓存并返回
  • 看源码:

什么时候从 mChangedScrap 中获取?

  • 如果 mAttachedScrap 没有命中缓存,且当缓存起来的 ViewHodlerpositionidRecyclerView 中要加载的 positionid 对应上的时候,就从 mChangedScrap 中获取缓存并返回
  • 看源码

CachedView

用来缓存已经滑出或即将滑进屏幕的 ViewHolder 有默认大小,为 2 在这里的缓存,不会擦除数据,所以复用时候,不需要重新 onBindViewHolder()

CachedView 是什么结构,其最大容量为多少?

  • 一个 ArrayList ,具体看源码定义

什么时候会加入 mCachedViewmCachedView 满了之后,应该怎么办?

  • ViewHolder 滑出屏幕时,会把滑出的 ViewHolder 加入到 mCachedView
  • 如果 mCachedView 满了,就需要将下标为 0 的 ViewHolder 加入 mRecyclerPool 中,并在 mCachedView 中将其移除(之所以不是移除最先加入 mCachedView 的元素,是因为,先加入的 ViewHolder 有可能是 pre-fetch 出来的 ViewHolder,而在插入 ViewHolder 时,是会跳过改元素,并插入到其前面的)
  • 需要注意的是,如果是对 ViewHolder 的状态是 REMOVEINVALIDUPDATEADAPTER_POSITION_UNKNOWN 时,是不会将其加入 mCachedView 中的

根据什么判断 CachedView 中的 ViewHodler 可以被复用?

  • 根据 ItemIdItemViewType 是否与要加载的 ViewHolder 相同,相同就复用,不相同就不复用
  • 看源码

什么时候从 mCachedView 中获取缓存?

  • 当从 Scrap 中获取不到时,则尝试从 mCachedView 中获取

ViewCacheExtension

Google 官方预留出来的给开发者用于自定义缓存策略的扩展接口

RecycledViewPool

第四级缓存,根据 viewType 进行缓存,每个 viewType 都可以指定最大缓存个数 这一级的缓存里面,会擦除掉 ViewHolder 中的数据,也就是复用时候需要执行 onBindViewHolder() 重新绑定数据

如果要在多个 RecyclerView 中共享同一个 RecycledViewPool,应该怎么做?

  • 在使用 RecyclerView 时,指定相应的 RecycledViewPool
  • 看源码

RecycledViewPool 的数据结构是怎么样的?

  • 具体是一个以 viewTypekeySparseArray(一个更加高效的 Map),value 是一个 ArrayList<ViewHolder>
  • 看源码

如果对于一个 ViewType 的缓存个数,超过了 DEFAULT_MAX_SCRAP,会怎么样?

  • 会立即返回不进行缓存,而不是把旧的移除,因为是同一个 ViewTypeViewHolder,没有必要进行这样的操作
  • 看源码

各级缓存之间的关系

  • 简单来说,就是在需要 ViewHolder 时,依次从第一级到第四级缓存中找,如果发现匹配的,就直接返回,否则就新建一个
  • 对于在屏幕中可见的 ViewHolder,缓存在 Scrap 缓存中
  • 对于因为滑动而消失在屏幕中的 ViewHolder,就缓存在 CachedView
  • CachedView 中数量达到上限后,就把 CachedView 中下标为 0 的 ViewHolder 根据不同的 ViewType 放入 RecycledViewPool

总结

  • RecyclerView 总共分为四级缓存,如果是在 mAttachedScrapmCachedView 之中的缓存,就可以直接复用,而不需要重新走 onBindViewHolder(),如果是在 mChangedScrapViewCacheExtensionRecycledViewPool 中的缓存,就需要重新走 onBindViewHolder() 将视图与新的数据进行绑定

参考/推荐

Jetpack LiveData源码及原理解析Jetpack ViewModel源码及原理解析
Loading...
Announcement
暂时没有公告!