RecycleView更清晰地观察缓存

为了更清晰的观察 recycleView 缓存复用,在看到有一篇文章是打印缓存相关信息后,觉着确实是一种办法,比打断点更合适查看缓存的变化情况,就尝试自己写了写。

这里我主要利用反射观察的是:mCachedViewsrecycleViewPool 里面的变化情况。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public void getInfo(){
try{
Class<?> class1 = Class.forName("android.support.v7.widget.RecyclerView");
Field field = class1.getDeclaredField("mRecycler");
field.setAccessible(true);

RecyclerView.Recycler recycler = (RecyclerView.Recycler) field.get(recyclerView);
Field field1 = recycler.getClass().getDeclaredField("mCachedViews");
field1.setAccessible(true);
ArrayList<RecyclerView.ViewHolder> mCachedViews = (ArrayList<RecyclerView.ViewHolder>) field1.get(recycler);

for(int i=0;i<mCachedViews.size();i++){

Log.i("xx","mCachedViews::"+mCachedViews.get(i));
}


RecyclerView.RecycledViewPool viewPool = recyclerView.getRecycledViewPool();
//这里之所以能获取到 RecycledViewPool中的mScrpa,需要新建一个android.support.v7.widget包,将该类放入该包下即可访问到。
SparseArray<RecyclerView.RecycledViewPool.ScrapData> list = viewPool.mScrap;
//这里传入的类型type 是我在adapter中定义的,如下:
//@Override
//public int getItemViewType(int position) {
//return 666;
//}
RecyclerView.RecycledViewPool.ScrapData spra = list.get(666);

Field field2 = spra.getClass().getDeclaredField("mScrapHeap");
ArrayList<RecyclerView.ViewHolder> mScrapHeap= (ArrayList<RecyclerView.ViewHolder>) field2.get(spra);
for(int i=0;i<mScrapHeap.size();i++){
Log.i("xx","mScrapHeapInfo::"+mScrapHeap.get(i));
}
Log.i("xx","=================================================");
}catch (Exception e) {
Log.i("xx", "error");
e.printStackTrace();
}
}

需要注意的是:反射获取

然后在 recycleView 滚动的时候,显示信息:

1
2
3
4
5
6
7
8
9
10
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// int recycleCount = recyclerView.getRecycledViewPool().getRecycledViewCount(666);
// Log.i("xx","recyclePoolCount::"+recycleCount);

getInfo();
}
});

还有可以在 adapter 重写方法: onViewRecycled(ViewHOlder holder) 方法,可以查看最新被回收的位置ViewHolder

1
2
3
4
5
@Override
public void onViewRecycled(ViewHolder holder) {
Log.i("xx","holder被回收了"+holder);//可以拿到被回收holder的相关信息
super.onViewRecycled(holder);
}

这样我们就可以通过日志的方式来近距离查看 recyclerView 复用回收。

我们假设这样一个场景:总共假设有20个item ,使用 GrideLayoutManager 每行显示5个,一个屏幕差不多显示两行,如下图所示:
image.png

我们在 adapteronCreateViewHolderonBindViewHolder 方法的地方都打印日志,并且重写 onViewRecycled 方法,查看 View 的回收情况。

当我们向下滑动,将第一行的数据滑出屏幕后,我们发现打印日志如下:
image.png

我们发现 2,3,4卡位的 ViewHolder 被回收了。这里的被回收是指添加到 RecycleViewPool 当中了。
此时 mCachedViews 中的缓存信息如下:

image.png

从日志上来看,它缓存了:1,0,15,16,17,18,19共7个卡位的数据。

这里补充一下:mCachedViews 的大小在源码定义中默认是:
static final int DEFAULT_CACHE_SIZE = 2;(当然也可以自己设置,通过 RecyclerView #setItemViewCacheSize,一般不设置)经过我的多次实验观察,当每行设置为5列时,mCachedViews.size == 2+ 5 = 7,当每行是4时,mCachedViews.size == 2+ 4 = 6,所以此时就是mCachedView.size 是7

问题1:为什么mCachedViews 缓存的卡位是1,0,15,16,17,18,19?
要回答这个问题,首先应该明白当我们在滑动第三行展示的时候,此时 RecyclerView 创建了第四行的 ViewHolder,即 15,16,17,18,19因为它们未曾展示到屏幕上,所以被 mCachedViews 缓存,继续滑动,第一行数据移出屏幕之外,也要回收了,0,1,2,3,4,5 这5个是将要被缓存,但是mCachedVIews 已经缓存了5个,势必只能再添加两个,两外三个卡位的 ViewHolder 将要被回收,GrideLayoutManager 默认从右往左回收:

第一次回收4,3卡位如图:
image.png

第二次回收2卡位如图:

因为此时缓存数量已到最大值7了,所以再次添加时,会移除第一个。代码如下:

1
2
3
4
5
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}

日志:

image.png

新缓存的2卡位被插入index = 1 的位置。
如此,我们就可以知道当1卡位会回收时,mCachedViews 中缓存的信息应该是,先移除第0个,也就是position= 3ViewHolder,插入到 index = 1 的位置,顺序应该是:2,1,15,16,17,18,19,好我们看下日志:
image.png

是的,不出我们所料,至此到最后我们就知道了,当0卡位被回收时,mCachedViews 的缓存信息就是:1,0,15,16,17,18,19. 被移除的ViewHolder 被添加到了 RecycleViewPool 当中了,从 RecycleViewPool 当中取出的 ViewHolder 类似于全新的,但是不会重新调用 onCreateViewHolder,会重新调用 onBindViewHolder 重新绑定数据。

-------------本文结束感谢您的阅读-------------