type
status
date
slug
summary
tags
category
password
icon

ListView 有二级缓存,RecyclerView 有四级缓存
ListView 的复用机制( RecycleBin ):

ListView 通过 RecycleBin 实现复用,mActiveViews 是一个 View数组,负责存储当前显示的 itemView,当页面数据未改变,但是需要重新显示时(当调用 requestLayout 这种被动刷新时),会直接从这里获取 itemView 复用。mScrapViews 是第二级缓存,是一个 ArrayList<View> 的数组(相当于二维数组),界面已经加载过的,但是已经不显示的 itemView 都将保存到这里,当需要复用时,从这里获取,如果上面两个缓存中都没有,就创建新的 itemView。
之所以 mScrapViews 是二级数组,是因为列表中 itemView 可能有不同的ViewType,需要根据不同的布局类型,做保存。
RecyclerView 组件职责

- RecyclerView 整体的管理者,以及本身作为 ViewGroup 的载体,最终负责展示各组件最终产⽣的 View 对象
- LayoutManager 负责 View 的测量、布局、触摸反馈,以及何时能够回收的策略
- ViewHolder 辅助“循环系统”中,可复⽤的 View 对象,例如减少 findViewById 的性能消耗。
- Recycler 抽象概念中如其名,回收管理者,负责为 LayoutManager 组件提供 ViewHolder 对象,在其内部会根据策略不同选择恰当状态的 ViewHolder 对象
- Adapter 将数据转化为 View 对象的转化⼯具,兼顾对 View 对象进⾏更新操作
- ItemAnimator Item 改变时的动画帮助⼯具
- ItemDecoration Item 增强⼯具,可以⾮常⽅便的⽤来绘制分割线,或者做⾼亮功能甚⾄⽤于做滚动条等
RecyclerView 简单运⾏机制
如下图所示:
- LayoutManager 通过
getViewForPosition()
向 Recycler 索要 View
- 当 Recycler 内部为空时,那么它会通过
Adapter
调⽤onCreateViewHolder()
创建出⼀个全新的携带 View 的 ViewHolder
- 接着 Adapter 会调⽤
onBindViewHolder()
为ViewHolder
绑定数据
- 然后 Recycler 就可以将这个已经绑定好数据的
ViewHolder
的 View 交给 LayoutManager
Recycler 中「多层」缓存

- CachedViews
⽤于缓存对应屏幕滑动时被「移⼊/移出」的 ViewHolder,默认⼤⼩为 2,可以通过
setItemViewCacheSize()
来修改⼤⼩,从这⼀层获取的 ViewHolder 是正确的position
,命中缓存则⽆需绑定数据可以直接使⽤
- RecycledViewPool
根据 ViewType 对 ViewHolder 进⾏缓存,每个 ViewType 默认⼤⼩为 5,可以通过
setMaxRecycledViews()
来修改⼤⼩,可以让多个 RecyclerView 共享同⼀个 RecycledViewPool,从这⼀层获取的 ViewHolder,position
不保证是正确的,命中缓存应该需要重新绑定数据
RecycledViewPool 可以看成一个
SparseArray<ArrayList<ViewHolder>>
SparseArray 是一个 key 只能为 Int 类型的 HashMap,这里使用是因为需要使用 R.layout.item 的 Id 作为 key,也就是 ViewType,之所以不用数组,是因为 Id 的值不是连续的。- ViewCacheExtension 可以⾃定义的缓存层,暂时没有⾮常有意义的实现
- AttachedScrap & ChangedScrap
为
pre/post-layout
提供⽀持的暂存层,没有⼤⼩限制 - AttachedScrap
- ChangedScrap

当某个 ItemView A[3] 调用 notifyItemChanged() 更新数据时,默认采用的是这个 ItemView 和新的 ItemView 完全替换:在 prelayout 时,会从 mAttachedScrap 和 mChangedScrap 中获取到暂存的A[3],并从 mCachedViews 或者 mRecyclerPool 中复用或通过createViewHolder 生成一个新的 A[3],新的 A[3] 执行入场动画,当前的 A[3] 执行出场动画
可以通过
androidx.recyclerview.widget.SimpleItemAnimator#setSupportsChangeAnimations(boolean)
方法开启/关闭简单动画,来防止上面更新 itemView 时闪烁的问题。
也可以通过androidx.recyclerview.widget.RecyclerView.Adapter#notifyItemChanged(int, java.lang.Object)
方法直接把新数据封装的新的 ViewHodler 保存到 mAttachedScrap 中,这样就不会走动画的逻辑,直接从暂存中获取,就实现了局部刷新核心机制 pre/post-layout

列表中有两个表项(1、2),删除 2,此时 3 会从屏幕底部平滑地移入并占据原来 2 的位置。
这是怎么做到的?RecyclerView 如何知道表项 3 的动画轨迹?虽然动画的终点已经有了(表项 2 的顶部),那起点呢?LayoutManager 只加载所有可见表项,在删除表项 2 之前,表项 3 处于不可见状态,它并不会被 layout。
对于这种情况 RecyclerView 的策略是 “执行两次 layout”:为动画前的表项先执行一次 pre-layout,将不可见的表项 3 也加载到布局中,形成一张布局快照(1、2、3)。再为动画后的表项执行一次 post-layout,同样形成一张布局快照(1、3)。比对两张快照中表项 3 的位置,就知道它该如何做动画了。

列表中 item 的改变,会执行旧的 item 执行离场动画,新的 item 执行入场动画。
notifyDataSetChanged()
notifyDataSetChanged() 的问题:
直接调⽤
notifyDataSetChanged()
后会把所有 ViewHolder 都放⼊ RecycledViewPool 中,RecycledViewPool 默认情况下每个 ViewType 缓存数量为 5,所以超出部分会被丢弃,这时候再次展示需要调⽤ Adapter#onCreateViewHolder
创建新的 ViewHolder,造成了不必要的开销解决方法:
通过
Adapter#setHasStableIds(true)
,然后正确重写对应的 getItemId()
,可以 ViewHolder 进⼊到 Scrap 层⽽不是 RecycledViewPool,这样就可以使得 Recycler 可以通过 itemId
来对 ViewHolder 进⾏合适缓存复⽤,避免了额外的开销。其他改变数据的方式
notifyItemChanged(int)
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)
- 作者:shuouyang
- 链接:https://notion-tree.vercel.app/article/a9235b00-1a76-4ae7-8eb9-34faffe0e336
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。