Android——RecyclerView缓存机制
Posted 虞美人体重90
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android——RecyclerView缓存机制相关的知识,希望对你有一定的参考价值。
RecyclerView是一种高度可定制的View控件,它可以用于显示大量的数据集合,用一种更有效的方式来管理数据的展示和滚动。
RecyclerView之所以那么高效有很大程度上归功于它的缓存机制。
一.使用步骤:
1.添加依赖
implementation \'androidx.recyclerview:recyclerview:1.1.0\' //recyclerview布局
2.layout文件中定义RecyclerView
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" />
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_ android:layout_ />
<androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_ android:layout_ />
3.配置布局管理器
RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this));
4.创建adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> private List<String> mData; public MyAdapter(List<String> data) mData = data; @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false); return new ViewHolder(view); @Override public void onBindViewHolder(ViewHolder holder, int position) holder.mTextView.setText(mData.get(position)); @Override public int getItemCount() return mData.size(); public static class ViewHolder extends RecyclerView.ViewHolder private TextView mTextView; public ViewHolder(View itemView) super(itemView); mTextView = (TextView) itemView.findViewById(R.id.text_view);
5.为RecyclerView设置adapter
RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this)); MyAdapter adapter = new MyAdapter(data); recyclerView.setAdapter(adapter);
二.缓存机制
RecyclerView把列表视图展示出来的过程主要分为三步:
1.createViewHolder:创建条目对应的视图
2.bindViewHolder:为条目视图绑定数据
3.renderViewHolder:将绑定好数据的条目视图展现出来
其中createViewHolder耗时最长,因此recyclerView设计了一套缓存机制来提高列表视图的性能。
①一级
缓存
当列表处于屏幕内时,屏幕中显示的ViewHolder会缓存在mAttachedScrap、mChangedScrap中 :
mChangedScrap 表示数据已经改变的ViewHolder列表
mAttachedScrap 表示未与RecyclerView分离的ViewHolder列表
该级缓存主要用于缓存出现在屏幕内的item,当我们通过notifyItemRemoved(),notifyItemChanged()通知item发生变化的时候,通过mAttachedScrap缓存没有发生变化的ViewHolder,其他的则由mChangedScrap缓存,添加itemView的时候快速从里面取出,完成局部刷新。源码如下:
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);
源码分析:
对于页面中显示的Item,当调用 LayoutManager 类的 onLayoutChildren() 方法对views进行布局,这时会将RecyclerView上的items全部暂存到一个 ArrayList 集合,这里的数据是没有做修改的,所以不用重新绑定 Adapter。而如果其他情况比如调用了 notifyItemChanged() 和 notifyItemRangeChanged() 来通知数据发生了更新,数据或位置发生改变,那么该ViewHolder会被缓存到mChangedScrap中,这里存储的是发生了变化的ViewHolder,所以要重新走Adapter的绑定方法。
图解:
图中的itemB删除掉,然后itemC,itemD依次移动上来,这里itemA和itemB前后参数没有发生变化(虽然itemB被移除了,但移除的时候它还是有效的,会被打上REMOVED标签,表示它是要删除的),所以他们两个存储到mAttachedscrap(),而itemC和itemD的位置发生了改变,所以他俩要存到mChangedScrap()中去。总结来说,删除itemB时,ABCD都会进入Scrap缓存,删除后,会从Scrap中将ACD取出,A的位置和数据都没有发生变化,CD的位置发生了变化但数据还是原封不动。
文章来源:https://blog.csdn.net/m0_51276753/article/details/125667231
复用
ViewHolder的复用是有顺序的,首先会判断是否预布局。
如果是就从一级缓存中的mChangedScrap()中获取。
如果没获取到就去mAttachScrap()和二级缓存中找。
而一级缓存之所以说轻量,首先是因为它只针对当前页面显示的这些item,其次是因为它用完就会清空缓存,不占空间,效率也快。
所以通知数据更新我们推荐使用notifyItemChanged(),实现局部刷新,用的是一级缓存来实现复用。而如果我们调用notifyDataChanged()来通知更新,会使数据全部进行刷新,不会走Scrap,性能低下。
②二级
缓存
当列表滑动出了屏幕时,ViewHolder会被缓存在 mCachedViews列表 ,其大小由mViewCacheMax决定,默认DEFAULT_CACHE_SIZE为2,可通过Recyclerview.setItemViewCacheSize()动态设置。
当一个viewHolder从屏幕中消失时,会把它先放进mCachedViews中,这个时候,其数据(position等)、状态均未丢失,当列表滑动,需要该position的viewHolder展示时,会先从mCachedViews中找该实例,如果找到了,那就拿出来进行展示。mCachedViews是一个缓存列表,用于移出屏幕表项的回收和复用,不会清空数据。
复用 查找顺序:先判断是否为预布局,如果是则从一级缓存中的mChangedScrap()中获取。如果不是,则进入一级缓存的mAttachScrap()中查找,mAttachScrap()没有就进入二级缓存mCachedViews列表查找。
// Search in our first-level recycled view cache. 官方说这里是第一级,但在我们日常使用中还是称他为第二级缓存 // 查找过程
// 这是根据position来取 final int cacheSize = mCachedViews.size(); for (int i = 0; i < cacheSize; i++) final ViewHolder holder = mCachedViews.get(i); // invalid view holders may be in cache if adapter has stable ids as they can be // retrieved via getScrapOrCachedViewForId // 这里要对索引进行判断,只有当位置对得上才能拿来复用, // 这也就意味着从mCatchedViews中取出的ViewHolder只能复用到指定的位置。 if (!holder.isInvalid() && holder.getLayoutPosition() == position && !holder.isAttachedToTransitionOverlay()) // 如果不在容量范围内,就把ViewHolder丢出去,丢到缓存池中。 if (!dryRun) mCachedViews.remove(i); if (DEBUG) Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position + ") found match in cache: " + holder); return holder;
mAttachedScrapp和mCachedViews都是需要进行索引判断,也就是说从这两个缓存中取出的ViewHolder只能复用到指定的位置。mCachedViews只能缓存屏幕外它容量大小的ViewHolder,超出容量的部分会被移除,丢到缓存池中。
默认大小为2,可通过Recyclerview.setItemViewCacheSize()动态设置。
③三级缓存
可以自己实现ViewCacheExtension类实现自定义缓存,可通过Recyclerview.setViewCacheExtension()设置。
如果我们自定义了一个缓存并且前面的一二级缓存没有找到ViewHolder,系统就会从我们自定义的这个缓存里去找ViewHolder。
④四级
缓存
ViewHolder首先会缓存在 mCachedViews 中,当超过了个数(比如默认为2), 就会按照先入先出原则被移出mCachedViews列表,添加到 RecycledViewPool 中。
RecycledViewPool 会根据每个ViewType把ViewHolder分别存储在不同的列表中,一个viewType对应一个mRecyclerPool,每个ViewType最多缓存DEFAULT_MAX_SCRAP = 5 个ViewHolder。
此时其数据、状态会被重置(position置为-1,bindViewHolder中的操作均失效)。
需要展示在屏幕中的viewholder会先在mCachedViews中找position对应的viewHolder,如果未找到,那么就去对应viewType的mRecyclerPool中找该对象。
RecycledViewPool源码:
public static class RecycledViewPool //同类ViewHolder缓存个数上限为5 private static final int DEFAULT_MAX_SCRAP = 5; // Tracks both pooled holders, as well as create/bind timing metadata for the given type. // 回收池中存放单个类型ViewHolder的容器 static class ScrapData //同类ViewHolder存储在ArrayList中 ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; //回收池中存放所有类型ViewHolder的容器 SparseArray<ScrapData> mScrap = new SparseArray<>(); ... //ViewHolder入池按viewType分类入池,一个类型的ViewType存放在一个ScrapData中 public void putRecycledView(ViewHolder scrap) final int viewType = scrap.getItemViewType(); final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap; //如果超限了,则放弃入池 if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) return; if (DEBUG && scrapHeap.contains(scrap)) throw new IllegalArgumentException("this scrap item already exists"); scrap.resetInternal(); //回收时,ViewHolder从列表尾部插入 scrapHeap.add(scrap); //从回收池中获取ViewHolder对象 public ViewHolder getRecycledView(int viewType) // 获取到viewType final ScrapData scrapData = mScrap.get(viewType); if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap; //复用时,从列表尾部获取ViewHolder(优先复用刚入池的ViewHoler) return scrapHeap.remove(scrapHeap.size() - 1); return null;
RecycledViewPool中的ViewHolder存储在SparseArray中,并且按viewType分类存储,同一类型的ViewHolder存放在一个ArrayList中。虽然没有了对索引的判断,但是从mRecyclerPool中取出的ViewHolder只能复用于相同viewType的表项。
复用
holder = getRecycledViewPool().getRecycledView(type); if (holder != null)
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST)
invalidateDisplayListInt(holder);
// 这里是getRecyclerViewPool(),主要作用就是new了一个RecyclerViewPool对象出来。 // 然后再根据type来从缓存池中获取对应类型的ViewHolder。 RecycledViewPool getRecycledViewPool() if (mRecyclerPool == null) mRecyclerPool = new RecycledViewPool(); return mRecyclerPool;
当ViewHolder从缓存池取出来后,判断holder是否为空,如果不为空,说明holder从缓存池中取出来了。那么就执行 holder.resetInternal(),该方法是将数据,状态重置。
把该对象拿出来,重新走一遍bindViewHolder、renderViewHolder,进行展示。
如果holder为空,则未找到,那么就走create、bind、renderViewHolder的完整流程。
深入理解Android RecyclerView的缓存机制
我们知道,RecyclerView在大量数据时依然可以丝滑般顺畅的滑动,那它究竟是怎么实现的呢,而RecyclerView之所以好用得益于它优秀的缓存机制。
我们知道,RecyclerView本身是一个ViewGroup,因此在滑动时就避免不了添加或移除子View(子View通过RecyclerView#Adapter中的onCreateViewHolder创建),如果每次使用子View都要去重新创建,肯定会影响滑动的流畅性,所以RecyclerView通过Recycler来缓存的是ViewHolder(内部包含子View),这样在滑动时可以复用子View,某些条件下还可以复用子View绑定的数据。所以本质上来说,RecyclerView之所以能够实现顺畅的滑动效果,是因为缓存机制,因为缓存减少了重复绘制View和绑定数据的时间,从而提高了滑动时的性能。
一、缓存
1.1、四级缓存
Recycler缓存ViewHolder对象有4个等级,优先级从高到底依次为:
- mAttachedScrap:缓存屏幕中可见范围的ViewHolder;
- mCachedViews:缓存滑动时即将与RecyclerView分离的ViewHolder,默认最大2个;
- ViewCacheExtension:自定义实现的缓存;
- RecycledViewPool :ViewHolder缓存池,可以支持不同的ViewType;
1.1.1 mAttachedScrap
mAttachedScrap存储的是当前屏幕中的
以上是关于Android——RecyclerView缓存机制的主要内容,如果未能解决你的问题,请参考以下文章
android 浅析RecyclerView回收复用机制及实战(仿探探效果)
android进阶篇02RecyclerView回收复用机制源码解析
Android:ListView 和RecyclerView区别