RecyclerView缓存复用机制
来到RecyclerView的Adapter代码中:
@NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.item, parent, false); Log.i("minfo", "onCreateViewHolder"); return new MyViewHolder(view); } @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { ((TextView)holder.textView).setText(datas.get(position)); Log.i("minfo", "onBindViewHolder"); }在onCreateViewHolder方法和onBindViewHolder方法打印,不停滑动recyclerView,会发现一个征象就是刚开始都会调两个方法,但是调用肯定次数之后,就只会调onBindViewHolder方法了。那么这个滑动过程中,就是在不停地回收复用。
首先要明确,RecyclerView缓存复用的是什么?缓存复用的是ViewHolder,ViewHolder是itemView的封装类。
Recycler 是 RecyclerView 的内部类,也是这套复用机制的焦点,显然 Recycler 的重要成员变量也都是用来缓存和复用 ViewHolder 的:
public final class Recycler { final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); ArrayList<ViewHolder> mChangedScrap = null; final ArrayList<ViewHolder> mCachedViews = new ArrayList<>(); RecycledViewPool mRecyclerPool; private ViewCacheExtension mViewCacheExtension;}复用机制
在滑动过程中,recyclerView不停在复用ViewHolder来表现item,那么就从recyclerView的滑动变乱入手源码,然后步步深入:
RecyclerView.onLayout(...)-> RecyclerView.dispatchLayout() -> RecyclerView.dispatchLayoutStep2() // do the actual layout of the views for the final state.-> mLayout.onLayoutChildren(mRecycler, mState) // mLayout 范例为 LayoutManager-> LinearLayoutManager.onLayoutChildren(...) // 以 LinearLayoutManager 为例-> LinearLayoutManager.fill(...) // The magic functions 添补给定的结构,表明很自大的说这个方法很独立,轻微改动就能作为资助类的一个公开方法,步伐员的快乐就是这么淳厚无华。-> LinearLayoutManager.layoutChunk(recycler, layoutState) // 循环调用,每次调用添补一个 ItemView 到 RV-> LinearLayoutManager.LayoutState.next(recycler) -> RecyclerView.Recycler.getViewForPosition(int) // 回到主角了,通过 Recycler 获取指定位置的 ItemView -> Recycler.getViewForPosition(int, boolean) // 调用下面方法获取 ViewHolder,并返回上面需要的 viewHolder.itemView -> Recycler.tryGetViewHolderForPositionByDeadline(...)终极调用 tryGetViewHolderForPositionByDeadline方法来获取ViewHolder,这个方法内里是复用的焦点,方法部分代码:
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) { if (mState.isPreLayout()) { // 从 mChangedScrap 内里去获取 ViewHolder,动画干系 holder = getChangedScrapViewForPosition(position); } if (holder == null) { // mAttachedScrap、 mHiddenViews、mCachedViews 获取 ViewHolder // 这个 mHiddenViews 是用来做动画期间的复用 holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); } if (holder == null) { final int type = mAdapter.getItemViewType(offsetPosition); // 假如 Adapter 的 hasStableIds 方法返回为 true // 优先通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 探求 if (mAdapter.hasStableIds()) { holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); } if (holder == null && mViewCacheExtension != null) { //从自界说缓存获取 View view = mViewCacheExtension getViewForPositionAndType(this, position, type); holder = getChildViewHolder(view); } } if (holder == null) { //从 RecycledViewPool 获取 ViewHolder holder = getRecycledViewPool().getRecycledView(type); } if (holder == null) { // 假如四级缓存里都没有,那就再调用createViewHolder创建ViewHolder holder = mAdapter.createViewHolder(RecyclerView.this, type); }}联合RecyclerView类中的源码及表明可知,Recycler会依次从mChangedScrap/mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool中实行获取指定位置或ID的ViewHolder对象以供重用,假如全都获取不到则直接重新创建。
缓存机制
统共有四级缓存,按照优先级分:
一级缓存的代码:
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); } }scrap是用来生存被rv移撤除但迩来又立刻要利用的缓存,好比说rv中自带item的动画效果。
mChangedScrap/mAttachedScrap
轻微仔细看的话就能发现scrap缓存有两个成员mChangedScrap和mAttachedScrap,它们生存的对象有些不一样,一样寻常调用adapter的notifyItemRangeChanged被移除的viewholder会生存到mChangedScrap,别的的notify系列方法(不包罗notifyDataSetChanged)移除的viewholder会被生存到mAttachedScrap中。用来缓存还在屏幕内的viewholder。
二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder,默认环境下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量巨细。假如 mCachedViews 的容量已满,则会根据 先辈先出 的规则移除旧 ViewHolder,再将viewHolder添加进RecycledViewPool中。
三级缓存:ViewCacheExtension ,开辟给用户的自界说扩展缓存,需要用户自己管理 View 的创建和缓存。通过提供给开辟者抽象方法来自己实现缓存。
四级缓存:RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中假如存不下新的 ViewHolder 时,会从cachedView缓存中移除,然后将移除的 ViewHolder 存入RecyclerViewPool 中。
将缓存viewHolder添加到recyclerViewPool中,根据itemtype获取对应的ScrapData数据,内里存储着该itemType的缓存聚集。RecycledViewPool默认巨细为5,当大于了最大容量,就不再缓存。
可以通过以下方式修改RecycledViewPool的缓存巨细:
RecyclerView.getRecycledViewPool().setMaxRecycledViews(int viewType, int max); 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(); scrapHeap.add(scrap); } //根据itemtype获取对应的ScrapData数据private ScrapData getScrapDataForType(int viewType) { ScrapData scrapData = mScrap.get(viewType); if (scrapData == null) { scrapData = new ScrapData(); mScrap.put(viewType, scrapData); } return scrapData; }mCachedViews二级缓存与四级缓存RecycledViewPool的区别:
cacheView缓存的viewHolder带有数据,而四级缓存已经清空了数据,只是缓存了viewHolder,从 RecyclerViewPool 中取出来的 ViewHolder 需要重新实行 bindViewHolder才气利用。以是二级缓存的复用速率会比四级缓存的复用速率更高。
mCachedViews只会缓存2个viewholder,就是屏幕滑出去的上面一个item和下面一个item。当上面一个item刚滑出去大概下面一个item刚滑出去,缓存在mCachedViews内里,内容不会被清空。此时再把这个item滑入屏幕内,不会走onBindViewHolder,由于数据还在。而随着往上或往下继承滑动更多,移除屏幕的item高出2个,mCachedViews缓存容量不足,就会将滑出屏幕的viewholder往recyclerViewPool中缓存。而把这些更多的item滑回屏幕时,这些缓存的viewholder数据已经被清掉,以是会调用onBindViewHolder方法重设数据
画图总结:
参考:
RecyclerView 的缓存复用机制
https://www.bilibili.com/video/BV1Yb4y1R7xn?p=3&spm_id_from=pageDriver&vd_source=40c24e77b23dc2e50de2b7c87c6fed59 |