RecyclerView 缓存机制 | 如何复用表项? - 掘金
RecycledViewPool
中的ViewHolder
存储在SparseArray
中,并且按viewType
分类存储(即是Adapter.getItemViewType()的返回值),同一类型的ViewHolder
存放在ArrayList
中,且默认最多存储5个。
在mRecyclerPool
成功获得ViewHolder
对象后,立即调用holder.resetInternal();对其重置(将flag置0)。这样就满足了绑定数据的判断条件(因为0和非0位与之后必然为0)。 所以,从mRecyclerPool
中复用的ViewHolder
需要重新绑定数据。
mCachedViews
是离屏缓存,用于缓存指定位置的 ViewHolder ,只有“列表回滚”这一种场景(刚滚出屏幕的表项再次进入屏幕),才有可能命中该缓存。该缓存存放在默认大小为 2 的ArrayList
中。从mCachedViews
中复用的ViewHolder
不需要重新绑定数据。
RecyclerView 缓存机制 | 回收些什么? - 掘金
RecyclerView 缓存机制 | 回收到哪去? - 掘金
mCachedViews
有点像“回收池预备队列”,即总是先回收到mCachedViews
,当它放不下的时候,按照先进先出原则将最先进入的ViewHolder
存入回收池RecycledViewPool。
RecyclerView缓存机制 | scrap view 的生命周期 - 掘金
mAttachedScrap
生命周期起始于RecyclerView
布局开始,终止于RecyclerView
布局结束。
-
Recycler
有4个层次用于缓存ViewHolder
对象,优先级从高到底依次为ArrayList<ViewHolder> mAttachedScrap
、ArrayList<ViewHolder> mCachedViews
、ViewCacheExtension mViewCacheExtension
、RecycledViewPool mRecyclerPool
。如果四层缓存都未命中,则重新创建并绑定ViewHolder
对象 -
缓存性能:
缓存 重新创建 ViewHolder
重新绑定数据 mAttachedScrap false false mCachedViews false false mRecyclerPool false true -
缓存容量:
mAttachedScrap
:没有大小限制,但最多包含屏幕可见表项。mCachedViews
:默认大小限制为2,放不下时,按照先进先出原则将最先进入的ViewHolder
存入回收池以腾出空间。mRecyclerPool
:对ViewHolder
按viewType
分类存储(通过SparseArray
),同类ViewHolder
存储在默认大小为5的ArrayList
中。
-
缓存用途:
mAttachedScrap
:用于布局过程中屏幕可见表项的回收和复用。mCachedViews
:用于移出屏幕表项的回收和复用,且只能用于指定位置的表项,有点像“回收池预备队列”,即总是先回收到mCachedViews
,当它放不下的时候,按照先进先出原则将最先进入的ViewHolder
存入回收池。mRecyclerPool
:用于移出屏幕表项的回收和复用,且只能用于指定viewType
的表项
-
缓存结构:
mAttachedScrap
:ArrayList<ViewHolder>
mCachedViews
:ArrayList<ViewHolder>
mRecyclerPool
:对ViewHolder
按viewType
分类存储在SparseArray<ScrapData>
中,同类ViewHolder
存储在ScrapData
中的ArrayList
中。
读源码长知识 | 更好的 RecyclerView 表项点击监听器 - 掘金
策略模式应用 | 每当为 RecyclerView 新增类型时就很抓狂 - 掘金
更好的 RecyclerView 表项子控件点击监听器 - 掘金
更高效地刷新 RecyclerView | DiffUtil二次封装 - 掘金
换一个思路,超简单的RecyclerView预加载 - 掘金
RecyclerView 动画原理 | 换个姿势看源码(pre-layout) - 掘金
RecyclerView 动画原理 | pre-layout,post-layout 与 scrap 缓存的关系 - 掘金
-
RecyclerView
为了实现表项动画,进行了 2 次布局(预布局 + 后布局),在源码上表现为LayoutManager.onLayoutChildren()
被调用 2 次。 -
State.mInPreLayout
用于标识是否在预布局阶段。预布局的生命周期始于RecyclerView.dispatchLayoutStep1()
,终于RecyclerView.dispatchLayoutStep2()
。 -
在预布局阶段,循环填充表项时,若遇到被移除的表项,则会忽略它占用的空间,多余空间被用来加载额外的表项,这些表项在屏幕之外,本来不会被加载。
RecyclerView 动画原理 | 如何存储并应用动画属性值? - 掘金
-
RecyclerView 将表项动画数据封装了两层,依次是
ItemHolderInfo
和InfoRecord
,它们记录了列表预布局和后布局表项的位置信息,即表项矩形区域与列表左上角的相对位置,它还用一个int
类型的标志位来记录表项经历了哪些布局阶段,以判断表项应该做的动画类型(出现,消失,保持)。 InfoRecord
被集中存放在一个商店类ViewInfoStore
中。所有参与动画的表项的ViewHolder
与InfoRecord
都会以键值对的形式存储其中。- RecyclerView 在布局的第三阶段会遍历商店类中所有的键值对,以
InfoRecord
中的标志位为依据,判断执行哪种动画。表项预布局和后布局的位置信息会一并传递给RecyclerView.ItemAnimator
,以触发动画。 RecyclerView.ItemAnimator
收到动画指令和数据后,又将他们封装为MoveInfo
,不同类型的动画被存储在不同的MoveInfo
列表中。然后将执行动画的逻辑抛到 Choreographer 的动画队列中,当下一个垂直同步信号到来时,Choreographer 从动画队列中取出并执行表项动画,执行动画即遍历所有的MoveInfo
列表,为每一个MoveInfo
构建 ViewPropertyAnimator 实例并启动动画。
RecyclerView 面试题 | 滚动时表项是如何被填充或回收的? - 掘金
-
RecyclerView 在滚动发生之前,会根据预计滚动位移大小来决定需要向列表中填充多少新的表项。
-
RecyclerView 填充表项是通过
while
循环一个一个实现的,当列表没有剩余空间时,填充表项也就结束了。 -
RecyclerView 滑动发生之前,会计算出一条limit 隐形线,它是决定哪些表项该被回收的重要依据。它可以理解为:隐形线当前所在位置,在滚动完成后会和列表顶部重叠
-
limit 隐形线的初始值 = 列表当前可见表项的底部到列表底部的距离,即列表在不填充新表项时,可以滑动的最大距离。每一个新填充表项消耗的像素值都会被追加到 limit 值之上,即limit 隐形线会随着新表项的填充而不断地下移。
-
触发回收逻辑时,会遍历当前所有表项,若某表项的底部位于limit 隐形线下方,则该表项上方的所有表项都会被回收。
RecyclerView 面试题 | 哪些情况下表项会被回收到缓存池? - 掘金
RecyclerView 性能优化 | 把加载表项耗时减半 (一) - 掘金
Window.addOnFrameMetricsAvailableListener()
方法可以监听最近 120 帧的绘制耗时。
RecyclerView 性能优化 | 把加载表项耗时减半 (二) - 掘金
RecyclerView 性能优化 | 把加载表项耗时减半 (三) - 掘金
RecyclerView 的滚动是怎么实现的?(一)| 解锁阅读源码新姿势 - 掘金
RecyclerView 的滚动时怎么实现的?(二)| Fling - 掘金
RecyclerView 刷新列表数据的 notifyDataSetChanged() 为什么是昂贵的? - 掘金
优化嵌套的RecyclerView
可以给RecyclerVIew设置自定义的视图池,代码看起来像这样:
public OuterRecyclerViewAdapter(List<Item> items) {
//Constructor stuff
viewPool = new RecyclerView.RecycledViewPool();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//Create viewHolder etc
holder.innerRecyclerView.setRecycledViewPool(viewPool);
}