一.简介
RecyclerView的回收复用机制内部实现都是由Recycler内部类实现。与ListView和GridView不同的是RecyclerView缓存的是ViewHolder。而ListView和GridView缓存的是View。
RecyclerView继承ViewGroup类。实现了ScrollingView接口和NestedScrollingChild2接口。也就是说其实RecyclerView其实是一个自定义的可以滑动的ViewGroup。
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
...
}
ListView缓存机制详解
https://blog.csdn.net/weixin_37730482/article/details/79351927
二.源码分析
Recycler类源码
public final class Recycler {
//存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder
final ArrayList<RecyclerView.ViewHolder> mAttachedScrap = new ArrayList<>();
//表示数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时需要改变的 ViewHolder。
ArrayList<RecyclerView.ViewHolder> mChangedScrap = null;
final ArrayList<RecyclerView.ViewHolder> mCachedViews = new ArrayList<RecyclerView.ViewHolder>();
private final List<RecyclerView.ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
RecyclerView.RecycledViewPool mRecyclerPool;
private RecyclerView.ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
......
}
由其源码可知,Recycler 是 RecyclerView 的内部类。并且 Recycler 的主要成员变量也都是用来缓存和复用 ViewHolder 的。
<1> mAttachedScrap集合和mChangedScrap集合
这两个集合对象都属于Scrap 缓存列表。是 RecyclerView 最先查找 ViewHolder 地方,是RecyclerView的第一层缓存。它跟 RecycledViewPool 或者 ViewCache 有很大的区别。两个集合只在布局阶段使用。其他时候它们是空的。布局完成之后,这两个缓存中的 viewHolder,会移到 mCacheView 或者 RecyclerViewPool 中。
两个集合添加时机是scrapView方法
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
而scrapView方法的调用时机在哪里呢?
StaggeredGridLayoutManager和LinearLayoutManager两个RecyclerView的LayoutManager中都会在各自的onLayoutChildren方法中调用detachAndScrapAttachedViews(recycler);方法。
以LinearLayoutManager为例
/**
* {@inheritDoc}
*/
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
detachAndScrapAttachedViews(recycler);
...
}
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
//调用Recycler类的scrapView方法
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
如果调用了 Adapter 的 notifyXXX 方法,会重新回调到 LayoutManager 的onLayoutChildren 方法里面, 而在 onLayoutChildren 方法里面,会将屏幕上所有的 ViewHolder 回收到 mAttachedScrap 和 mChangedScrap。
<2> mCachedViews 第二层缓存
用来缓存刚刚移出屏幕的缓存ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder。当其容量被充满同时又有新的数据添加的时候,会根据FIFO原则,把优先进入的缓存数据移出并放到下一级缓存中,然后再把新的数据添加进来。
如果使用GridLayoutManager建议设置为列的个数。
//声明
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
//默认最大值
static final int DEFAULT_CACHE_SIZE = 2;
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
//修改最大值
/**
* Set the maximum number of detached, valid views we should retain for later use.
*
* @param viewCount Number of views to keep before sending views to the shared pool
*/
public void setViewCacheSize(int viewCount) {
mRequestedCacheMax = viewCount;
updateViewCacheSize();
}
<3> RecycledViewPool 第三层缓存
RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把 ViewHolder 存入RecyclerViewPool 中。当第二层Cache缓存满了以后会根据FIFO(先进先出)的规则把Cache先缓存进去的ViewHolder移出并缓存到RecycledViewPool中。
(1) 按照 Type 来查找 ViewHolder。
(2) 每个 Type 默认最多缓存 5 个。
(3) 可以多个 RecyclerView 共享 RecycledViewPool。
<4>mCachedViews缓存和RecycledViewPool缓存 区别
(1) 从Cache里面移出的ViewHolder再存入RecycledViewPool之前ViewHolder的数据会被全部重置,相当于一个新的ViewHolder,而且Cache是根据position来获取ViewHolder;
(2) 而RecycledViewPool是根据itemType获取的,如果没有重写getItemType(),itemType是默认的。因为RecycledViewPool缓存的ViewHolder是全新的,所以取出来的时候需要走onBindViewHolder()。
三.缓存优化实践
1.尽量使用 notifyItemXxx 方法进行细粒度的通知更新,而不是 notifyDatasetChanged。
2.局部刷新使用DiffUtil工具类。
详情
https://blog.csdn.net/weixin_37730482/article/details/72822870
https://blog.csdn.net/weixin_37730482/article/details/71173037
https://blog.csdn.net/weixin_37730482/article/details/79391171