前言
文中材料都来自网络查找,有错的话可以评论交流
一、RecyclerView的四级缓存是什么?
首先需要对RecyclerView如何滚动显示其子项有一定的了解,这与ViewGroup的显示流程有关,ViewGroup需要依次走过onMeasure()方法、onLayout()方法、onDraw()方法,依次测量,布局,绘制。RecyclerView监听到滑动的操作后,重新调用onLayout()来排列和布局子 View ,这样就有了滚动的能力。
-
一级缓存:mAttachedScrap 和 mChangedScrap ,为LayoutManager每次布局子View之前,那些已经添加到RecyclerView中的Item以及被删除的Item的临时存放集合,一级缓存都是屏幕内的。
- mAttachedScrap 存储的是当前还在屏幕中的 ViewHolder。
- mChangedScrap 存储的是当前还在屏幕中、数据已经改变的 ViewHolder 。 -
二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder。
- 默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小
如果 mCachedViews 的容量已满,则会根据 FIFO (先进先出)的规则移除旧 ViewHolder。
取出ViewHolder(即将重用)时无须重新绑定数据(不用执行onBindViewHolder方法)
-
三级缓存:ViewCacheExtension ,开发给用户的自定义扩展缓存,需要用户自己管理
View 的创建和缓存.相当不常用。
-
四级缓存:RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的
ViewHolder 时,就会把 ViewHolder 存入RecyclerViewPool 中。- 按照 Type 来查找 ViewHolder,与之相比,上面的都是通过index去获取 ViewHolder的
- 每个 Type 默认最多缓存 5 个
- 可以多个 RecyclerView 共享 RecycledViewPool
二、流程分析
1.前提条件
用了一个Recyclerview,右边添加一个收藏按钮,列表数据item类里设置一个标志,如下代码,若istrue为true则收藏显示,若为false,则不显示。效果如下图,只有第一个图片设置为true。
public test(String text, int imageId, boolean istrue) {//item子类
this.text = text;
this.imageId = imageId;
this.istrue = istrue;
}
tests.add(new test("我的天啊"+j++,R.drawable.soucang1,true));//只有第一个设置为true
for (int i=0;i<40;i++){
tests.add(new test("妈妈啊"+j++,R.drawable.soucang1,false));
tests.add(new test("爸爸啊"+j++,R.drawable.soucang1,false));
tests.add(new test("姐姐啊"+j++,R.drawable.soucang1,false));
}
但是当往下滑动会发现,下面的图片也显示了五角星收藏,这就是复用了之前绘制的图片,效果如下,仔细看会发现复用还有一定的规律。第1,13,25,37,42个显示五角星,每隔12个显示一个
但是当我不断上滑下滑,就会出现如下情况:五角星变多了
从上我们可以推断recyclerview的viewholder复用的流程。
2.Recyclerview复用流程
-
一级缓存,mAttachedScrap缓存的是屏幕内能看见的子项
-
如上面的例子,第一个为带星子项(带星星的那个子项),开始向上滑动,带星子项,进入二级缓存mCachedViews,第二个子项也看不见时,第二个子项也进入了二级缓存mCachedViews。
-
由于二级缓存mCachedViews缓存容量为2,此时二级缓存已满,根据先进先出原则,带星子项首先进入了四级缓存RecycledViewPool中。
-
四级缓存RecycledViewPool容量为5,当界面继续向上滑动,子项继续进入二级缓存mCachedViews,先进先出原则,二级缓存的子项溢出,进入四级缓存,直到四级缓存开始满了,也就是说此时,带星子项在四级缓存的最上面,即将溢出。
-
继续向上滑动,带星子项溢出被拿到界面,也就是说,我们复用了这个带星子项,这也就是为什么,我们明明只设置了一个带星子项,但会每隔12个就显示一个子项。当你一直向下滑动,因为它一直在被复用,所以你看到的始终是同一个子项。
3.复用流程2
onCreateViewHolder,onBindViewHolder里的代码如下:
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view= LayoutInflater.from(parent.getContext()).inflate(R.layout.recycle_item,parent,false);
ViewHolder viewHolder=new ViewHolder(view);
Log.d("傻", "onCreateViewHolder: ");
return viewHolder;
}
ReAdapter(List<test> list){
tests=list;
}
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
final test tes=tests.get(position);
Log.d("傻", "onBindViewHolder: "+"我要绑定数据"+position+tes.istrue);
holder.imageView.setImageResource(tes.imageId);
holder.textView.setText(tes.text);
if(tes.istrue){
holder.imageView.setVisibility(View.VISIBLE);
Log.d("傻", "onBindViewHolder: "+"我要求能看见图片"+position);
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("傻", "onClick: ");
holder.imageView.setImageResource(R.drawable.soucang2);
tes.istrue=true;
}
});
}
当向上滑动,其日志打印如下:
最开始,由于屏幕内和缓存池里并没有子项可以复用,所以经过onCreateViewHolder创建,onBindViewHolder绑定数据,创建了一个个viewholder。继续向下滑动,打印日志如下
可以看到后面没有再onCreateViewHolder,说明这些子项(ViewHolder)一直在被循环复用,只需要重新绑定数据就可以再度使用。
为什么不断来回滑动,带星子项变得越来越多?
因为,当你向上滑动,此时带星子项进入缓存池,当你再向上滑动,如果此时带星子项没有正好溢出,被复用到你设置的第一个应该拥有星星的子项时,就会重新绘制一个,此时缓存池有一个带星子项,屏幕内也有一个带星子项,当你不断来回滑动,就可能不断重新绘制一个带星子项,所以会变得越来越多。
注意
- 复用的其实是viewholder。这里我开始以为复用的是viewholder这个类,始终没理解,这个类和绘制出来的view有什么联系,后来我才发现,我把listview里的viewholder和recyclerview的holder弄混了。listview里的viewholder,是自己定义的类,内含一些数据变量,而recyclerview里的viewholder传进了View类的参数,itemview。
所以我们可以理解为复用的就是一个个绘制好的view或者说卡片。