Recycler 只被RecyclerView持有。
Recycler 被回收的ViewHolder,实际上是放入了RecyclerViewPool里面。
Recycler 只会操作ViewHolder,不去操作具体的View,即使在某些情况下传入view,也是通过view去找到与之对应的holder。
Recycler的缓存分三级,一级缓存保存在列表的数据,第二级是根据使用者是否配置来确定,三级缓存则是缓存池。
一级缓存分两种,被废弃的view(Scrap View),被回收的View(RecycledView)
被废弃的view(Scrap View)缓存
mAttachedScrap:其添加holder的地方为scrapView(); 条件 可重用的holder、已移除的holder、失效的holder、不是Update的holder。
mChangedScrap:与mAttachedScrap同样在scrapView()里面添加数据,只要不是上述情况就加入到这里面;
以上两种方法都是在unscrapView()方法里面对缓存进行移除。判断条件为holder.mInChangeScrap.
被回收的View(RecycledView)缓存
mCachedViews:添加缓存的方法recycleViewHolderInternal(),根据holder 可回收或强制回收,同时要保证最大可缓存数大于0,holder的标志必须 是FLAG_INVALID、FLAG_REMOVED、FLAG_UPDATE、FLAG_ADAPTER_POSITION_UNKNOWN之一,则会加入到缓存列表mCachedViews;不符合上述条件,那么则直接加入到RecycledViewPool缓存池。要注意的是,虽然mCachedViews是ArrayList,但是它不是无限制了添加holder,有一个maxCachedSize对它做了限制。
而从mCachedViews里移除holder时,一般会将其添加到RecycledViewPool 回收池里。holder在加入缓存池的时候,holder会与所属的RecyclerView解除关联,还会将holder所有设置过的数据设置回初始状态,另一种移除的情况是加入到RecyclerView展示的情况。
那么RecyclerView 根据什么判定用废弃还是回收呢?
看看recycleViewHolderInternal 与 scrapView 各自调用的地方. 最后在LayoutManger里找到了关键代码,
RecyclerView.LayoutManger.scrapOrRecycleView(), 它调用链:onLayoutChildren()=> detachAndScrapAttachedViews() => scrapOrRecycleView(),
见下代码:
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
...
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
很明显,被回收的是:holder失效 && 未移除 && hasStableIds未设置为true,
其他情况,被废弃。
其上还有两个函数要关注一下:removeViewAt(index)、detachViewAt(index),他们的内部都由ChildHelpter去执行具体的操作,相同点都会从mBucket里将view移除,不同点在于:removeViewAt(index)还会将index的view从mHiddenViews移除的动作。 由于ChildHelper内部维护了可见\不可见两列表,而scrap方式不去删除不可见表的数据,因此其复用性要高一些,这也是后文获取缓存的缓存为什么scrap在前的原因。
关于缓存池 RecyclerViewPool
缓存池,保存holder根据getItemViewType()类别分别保存,它也是有存储个数限制的,默认每个type保存最大限制个数为5个。
mScrap:以type作为key 保存对应类型的缓存列表。
ScrapData:缓存列表的载体,mScrapHeap才是真正的列表。同时它还保存了缓存的最大时长。
获取缓存过程
前面分析了holder是怎么缓存的,下面看看它是怎么从各级缓存里面将数据拿出来的。
关键代码点:Recycler.tryGetViewHolderForPositionByDeadline()
查找缓存顺序
- 在预布局状态,如果有改变的, 就从 mChangeScrap里面找寻。
- 没到找,根据位置position去找,从mAttachedScrap里面去找,没有还要去ChildHelper里面的不可见列表找,还找不到,去mCachedViews里面未失效找对应的位置的holder。
- 没找到,根据adapter.getItemId(),再从mAttachedScrap,再从mCachedViews里找。
- 还没找到,若配置了mViewCacheExtension对象,则从这里去找,一定会找到,找不到就会throw,若不配置则没有该步骤。
- 还没找到,最后到缓存池里找,
- 最后都没有找到,放弃治疗了,自己去创建一个viewHolder。
并不是这里找到了viewHolder就可以直接使用,需要对该holder设置一部分属性:
holder.mPreLayoutPosition,如果是预布局状态并且已绑定了view 才需要;
bound:holder未与RecyclerView、adapter绑定的情况,将mAdater与holder绑定,tryBindViewHolderByDeadline()。这种场景是从缓存池里取出的,因为在存入的时候是解除了关系并将holder设置回了初始状态,我们熟悉的ViewHolder.bindViewHolder()方法,在复用时就是这里调用的。