在本文我们继续学习RecyclerView缓存的相关知识。
缓存分为缓存取出和缓存存入,首先来分析下缓存取出:
一、缓存取出
在上一篇文章中,我们分析到了一个比较关键的方法--
layoutChunk,再来看下其源码:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//获取view
View view = layoutState.next(recycler);
//代码省略......
}
这里只保留了一行很重要的代码View view = layoutState.next(recycler);
,看下next方法。
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
//获取view
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
这个next方法就是从缓存中来获取view,首先是判断mScrapList是否为空,不为空就调用nextViewFromScrapList();方法。这里是以LinearLayoutManager来分析的,mScrapList是LinearLayoutManager的一个成员,它主要与动画相关,因此这里就不再过多的分析它了。接着往下看,调用了Recycler的getViewForPosition(int position)方法,Recycler是RecyclerView的一个内部类,它主要负责RecyclerView的缓存管理。看下这个内部类:
public final class Recycler {
final ArrayList mAttachedScrap = new ArrayList<>();
ArrayList mChangedScrap = null;
final ArrayList mCachedViews = new ArrayList();
private final List
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
//代码省略......
这里对几个成员做一下说明:
mAttachedScrap:存储当前还在屏幕中的ViewHolder,意思就是刚刚从屏幕中分离出来,然后又即将添加到屏幕上的ViewHolder。
比如说,RecyclerView数据发生更新,调用notifyDataSetChanged()方法刷新界面,在列表数据刷新的时候存储屏幕内被移除的ViewHolder。
mChangedScrap:存储的是当前被更新的ViewHolder,比如说调用了adapter调用notifyItemChanged方法。
mAttachedScrap和mChangedScrap共同组成第一级缓存。也叫屏幕内缓存,不参与RecyclerView在滑动状态下ViewHolder的回收和复用。
mCachedViews:默认大小为2,也叫离屏缓存。即在Recycler View滑动的时候,回收滑出屏幕的ViewHolder以及提供滑动到屏幕内的ViewHolder的复用。
mCachedViews构成第二级缓存。
mViewCacheExtension:是一个抽象静态类,用于充当附加的缓存池。当RecyclerView从第一级缓存中没有找到需要的view时,就会从ViewCacheExtension中查找。
不过这个缓存是由我们开发者自己维护的,如果没有设置这个缓存,就不会启用它。一般情况下我们也不会去设置它,系统已经预先提供了两级缓存了。
如果有特殊需求,比如要在调用系统的缓存池之前,返回一个特定的视图,才会用到它。
RecycledViewPool:非常强大的缓存,其默认大小为5,用于在多个嵌套的RecyclerView之间缓存共享的viewholder,接下来会介绍它。
}
参考网址:https://juejin.im/entry/5c66ce2b51882562c704ebad
从上面我们可以看到,RecyclerView缓存的是viewholder,这也是它跟ListView不同的地方,ListView需要我们手动判断缓存是否为空,而RecyclerView由于缓存的是viewholder,直接就在内部就帮我们判断好了,我们只需要使用即可。
重点看下RecycledViewPool这个缓存类:
/**
* RecycledViewPool lets you share Views between multiple RecyclerViews.
* 在多个RecyclerView之间共享缓存的viewholder
* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
* and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
*
* RecyclerView automatically creates a pool for itself if you don't provide one.
* 如果我们没有为RecyclerView设置RecycledViewPool,RecyclerView会自动为自己创建一个,当然了这个自己自动创建的只能自己单独使用
*/
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
/**
* Tracks both pooled holders, as well as create/bind timing metadata for the given type.
*
* Note that this tracks running averages of create/bind time across all RecyclerViews
* (and, indirectly, Adapters) that use this pool.
*
* 对于缓存的ViewHolder的统一管理
*/
static class ScrapData {
final ArrayList mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray mScrap = new SparseArray<>();
//代码省略......
/**
* Sets the maximum number of ViewHolders to hold in the pool before discarding.
* 设置RecyclerViewPool能缓存的ViewHolder的最大数量。
*/
public void setMaxRecycledViews(int viewType, int max) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mMaxScrap = max;
final ArrayList scrapHeap = scrapData.mScrapHeap;
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}
/**
* Returns the current number of Views held by the RecycledViewPool of the given view type.
* 获取RecyclerViewPool缓存的ViewHolder数量。
*/
public int getRecycledViewCount(int viewType) {
return getScrapDataForType(viewType).mScrapHeap.size();
}
/**
* Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are
* present.
* 从RecyclerViewPool中获取一个ViewHolder缓存,并将其从RecyclerViewPool缓存中移除。
*/
@Nullable
public ViewHolder getRecycledView(int viewType) {
//根据viewtype获取缓存
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList scrapHeap = scrapData.mScrapHeap;
//从pool中删除并返回符合条件的viewholder
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
//代码省略......
/**
* Add a scrap ViewHolder to the pool.
*
* If the pool is already full for that ViewHolder's type, it will be immediately discarded.
*
* @param scrap ViewHolder to be added to the pool.
*/
public void putRecycledView(ViewHolder scrap) {
//获取要缓存的viewholder的viewType
final int viewType = scrap.getItemViewType();
//根据viewType来获取viewholder要保存的位置
final ArrayList 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);
}
//代码省略......
private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}
}
从名字上看,RecycledViewPool是一个缓存池,其内部实际上是通过一个默认大小为5的ArrayList来实现的,我们也可以手动设置其缓存容量。然后ArrayList又是用ScrapData这个内部类来统一管理,最后再将这个ScrapData放到SparseArray中,组成缓存的最终形势。我们在实现了自己的RecycledViewPool后,只需要调用RecyclerView#setRecycledViewPool(RecycledViewPool)
就可以为多个RecyclerView设置共享的RecycledViewPool。
顺带说下,这个SparseArray内部其实是由两个数组来实现的,一个数组存储键,另一个数组存储值,其效率比HashMap要高一点,但是只能存储少量数据,数据量大的话其效率不如HashMap。在这里,键是viewType,值是scrapData。
分析完了Recycler及RecycledViewPool后,我们继续跟进getViewForPosition方法:
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
最终调用了 tryGetViewHolderForPositionByDeadline方法。
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
//代码省略......
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
//默认情况下是false,只有动画的情况下才是true
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
//第一次尝试,从attachScrap和cachedSCrap中查找viewholder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
//如果获取到的viewholder不可用于当前这个位置
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
//如果viewholder可以从scrap / cache 中移除
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
//移除这个viewholder
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
//将viewholder置空,表示没有查找到,继续往下查找
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//代码省略......
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
//第二次尝试,对应hasStableId情况
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
//如果第二次尝试后holder仍为空,并且我们设置了自定义缓存(ViewCacheExtension)
if (holder == null && mViewCacheExtension != null) {
//从ViewCacheExtension中查找
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
//根据查找到的view获取相应的viewholder
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder"
+ exceptionLabel());
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view." + exceptionLabel());
}
}
}
//前面的缓存均为获取到viewholder,从RecycledViewPool中查找
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
//从缓存中查找失败,调用adapter的createViewHolder方法创建viewholder
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
}
//代码省略......
return holder;
}
我们从上往下慢慢看:
根据position获取缓存
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
/**
* Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its
* {@link LayoutManager} layout items where they will be at the beginning of a set of
* predictive item animations.
* 在有动画情况下这里返回true,否则返回false
*/
public boolean isPreLayout() {
return mInPreLayout;
}
//从mChangedScrap缓存中获取viewholder
ViewHolder getChangedScrapViewForPosition(int position) {
// If pre-layout, check the changed scrap for an exact match.
final int changedScrapSize;
if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
return null;
}
// find by position
//按照位置查找
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// find by id
//按照id查找
if (mAdapter.hasStableIds()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
final long id = mAdapter.getItemId(offsetPosition);
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
}
return null;
}
首先判断isPreLayout()是否为true,这里主要是跟动画有关,如果为true,也就是有动画,则调用 getChangedScrapViewForPosition(position)方法,从这个方法的名字当中我们也可以看出是从changedScraps缓存中获取。
如果上面isPreLayout()为false时或者isPreLayout()为true并且没有获取到viewholder时,开始从从attachScrap和cachedScrap缓存中查找(这里是按照position来查找的)。
if (holder == null) {
//从attachScrap和cachedScrap缓存中查找viewholder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
//如果查找到viewholder不适合当前位置
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
//dryRun标记表示viewholder是否可以从缓存中移除,为false表示可以移除,这里传入的是false
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
//从缓存中移除viewholder
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
//将viewholder置空,继续下面的查找
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
这里重点关注下getScrapOrHiddenOrCachedHolderForPosition
这个方法,从名字就可以看出是按照position来查找缓存的。
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
//从mAttachedScrap中查找
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
//dryRun为false
if (!dryRun) {
//从hiddenView中查找view
View view = mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
// This View is good to be used. We just need to unhide, detach and move to the
// scrap list.
//根据查找到的view获取viewholder
final ViewHolder vh = getChildViewHolderInt(view);
//将view从hiddenView中移除
mChildHelper.unhide(view);
int layoutIndex = mChildHelper.indexOfChild(view);
if (layoutIndex == RecyclerView.NO_POSITION) {
throw new IllegalStateException("layout index should not be -1 after "
+ "unhiding a view:" + vh + exceptionLabel());
}
mChildHelper.detachViewFromParent(layoutIndex);
//将view添加到scrap缓存中(这里是添加到了mAttachedScrap或者mChangedScrap中)
scrapView(view);
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
// Search in our first-level recycled view cache.
//从mCachedViews中查找
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
//如果查找到viewholder是有效的,并且viewholder的位置和position的位置相同
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
if (!dryRun) {
//从缓存中删除这个viewholder
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
//返回holder
return holder;
}
}
return null;
}
简要总结下getScrapOrHiddenOrCachedHolderForPosition
的流程:
- 1、首先从mAttachedScrap中查找viewholder,如果找到了并且验证符合条件,直接返回。
- 2、否则从hiddenView中查找view,再根据这个view获取viewholder,并将view从hiddenView中移除,接着将viewholder保存到缓存中(mAttachedScrap或者mChangedScrap),最后返回。
- 3、如果1和2中都没有获取到,则从mCachedViews中查找viewholder,并校验这个viewholder是否满足条件,如果满足,将其从mCachedViews中删除,并返回viewholder。
沿着tryGetViewHolderForPositionByDeadline
方法继续往下分析,上面是按照position来查找缓存的,如果没有查找到,那么按照stableId再次进行查找。
根据itemId和viewType获取缓存
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//省略.......
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
首先通过mAdapter.getItemViewType(offsetPosition)这个方法获取type,这个getItemViewType也是我们设置RecyclerView列表多种布局的一个重要方法,并且后面所有缓存的获取都是根据这个type来的。也就是说前面介绍的也就是注释中 0)
和 1)
获取的缓存是没有区分type,接下来的缓存获取都是区分type的。
接下来判断mAdapter.hasStableIds()是否为true。
/**
* Returns true if this adapter publishes a unique long
value that can
* act as a key for the item at a given position in the data set. If that item is relocated
* in the data set, the ID returned for that item should be the same.
*
* @return true if this adapter's items have stable IDs
*/
public final boolean hasStableIds() {
return mHasStableIds;
}
这里关注下getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun)
这个方法,这里通过adapter的getItemId方法获取相应的itemId,这个getItemId方法我们可重写也可不重写。
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// Look in our attached views first
final int count = mAttachedScrap.size();
//从mAttachedScrap缓存中查找
for (int i = count - 1; i >= 0; i--) {
final ViewHolder holder = mAttachedScrap.get(i);
//判断缓存中的viewholder所在的itemId是否符合传入的itemId
if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
//判断type
if (type == holder.getItemViewType()) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
if (holder.isRemoved()) {
// this might be valid in two cases:
// > item is removed but we are in pre-layout pass
// >> do nothing. return as is. make sure we don't rebind
// > item is removed then added to another position and we are in
// post layout.
// >> remove removed and invalid flags, add update flag to rebind
// because item was invisible to us and we don't know what happened in
// between.
if (!mState.isPreLayout()) {
holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
}
}
//itemId和viewType都满足,直接返回holder
return holder;
} else if (!dryRun) {
//如果viewholder的itemId符合条件,但是viewType不符合条件
// if we are running animations, it is actually better to keep it in scrap
// but this would force layout manager to lay it out which would be bad.
// Recycle this scrap. Type mismatch.
//从缓存中移除viewholder
mAttachedScrap.remove(i);
removeDetachedView(holder.itemView, false);
//将viewholder缓存到RecyclerViewPool中
quickRecycleScrapView(holder.itemView);
}
}
}
// Search the first-level cache
//从mCachedViews缓存中查找
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
//判断缓存中的viewholder所在的itemId是否符合传入的itemId
if (holder.getItemId() == id) {
//判断type
if (type == holder.getItemViewType()) {
//如果viewholder的itemId符合条件,但是viewType不符合条件
if (!dryRun) {
//从缓存中移除viewholder
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
//将viewholder从mCachedViews中移除,并缓存到RecyclerViewPool中
recycleCachedViewAt(i);
return null;
}
}
}
return null;
}
这个方法其实和上面的getScrapOrHiddenOrCachedHolderForPosition
方法差不多,区别是该方法是按照itemId和viewType来判断的,而getScrapOrHiddenOrCachedHolderForPosition
是按照position来判断的。根据itemId和viewType来查找的大致流程如下:
从自定义缓存中查找(ViewCacheExtension)
当前面的查找都没有找到时,程序就来到了这里,从我们的自定义缓存(ViewCacheExtension)中来查找。
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
//从ViewCacheExtension中获取view
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
//根据view获取viewholder
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder"+ exceptionLabel());
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view." + exceptionLabel());
}
}
}
代码很简单,主要是看下这个ViewCacheExtension到底是啥:
/**
* ViewCacheExtension is a helper class to provide an additional layer of view caching that can
* be controlled by the developer.
*/
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
*/
public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
}
这里就一个抽象类,内部一个抽象方法。对于这个自定义缓存的介绍,直接引用了【进阶】RecyclerView源码解析(二)——缓存机制里面的评价:
1、首先这个类基本上没有什么限制,也就是说无论是缓存使用的数据结构还有缓存算法(LRU还是什么)完全自定义,都由开发者自己决定,这一点可以说既给了开发者很大的便利,也给开发者带来了很大的隐患。
2、对于平常的缓存,我们的理解在怎么说至少get-add|push-pop都是成对出现,为什么这样说的,也就是缓存至少有进也有出。而这里可以看到这里的抽象类只定义了出的方法,也就是只出不进,进的时机,大小,时效等完全没有规定。
在日常开发中,我们也很少去自定义这一级的缓存,因为它几乎用不上。
从RecyclerViewPool中查找
RecyclerView与ListView的另一个不同之处是RecyclerView提供了RecyclerViewPool,这是一个在多个RecyclerView之间提供共享view的缓存。比较常见的场景是RecyclerView和RecyclerView之间的嵌套,RecyclerViewPool为这些个嵌套的RecyclerView内view的整体复用提供了便利。
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
对于RecyclerViewPool在本文的前面已经有介绍,这里就不再说了,我们继续往下看。
调用adapter的createViewHolder创建ViewHolder
如果经过上面的多级缓存查找仍然没有查找到的话,程序最终会调用调用adapter的createViewHolder方法来创建一个viewholder。
if (holder == null) {
long start = getNanoTime();
//代码省略......
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
//代码省略......
}
没什么好说的,直接调用mAdapter.createViewHolder(RecyclerView.this, type)创建,在这个方法里面最终调用了我们继承并实现的onCreateViewHolder方法。
至此,RecyclerView的缓存取出过程总算是分析完了,不过这里也只是简要的介绍了下,还有很多细节由于水平限制没有详细介绍。最后再总结下:
- RecyclerView一共有三级缓存(也有人说是四级缓存,这个每个人的角度不一样,因此会有一些区别)。第一级缓存由mAttachedScrap,mChangedScrap,mCachedViews共同组成;第二级缓存是ViewCacheExtension;第三级缓存是RecyclerViewPool。
- RecyclerView在第一级缓存中查找的时候一共查找了两次。第一次是根据position来查找的,调用方法为
getScrapOrHiddenOrCachedHolderForPosition
;第二次是根据itemId和viewType来查找的,调用方法为getScrapOrCachedViewForId
。 - RecyclerView在根据itemId和viewType来查找缓存时,如果发现当viewholder的itemId符合条件而viewType不符合条件时,会将这个viewholder从mAttachedScrap或者mCachedViews中移动到RecyclerViewPool中(也就是将这个viewholder从mAttachedScrap或者mCachedViews中删除同时保存到RecyclerViewPool中)。
二、缓存存入
既然有缓存取出,也应该有缓存的存入。RecyclerView缓存的存入有一个很重要的方法:recycleView(View view)
/**
* Recycle a detached view. The specified view will be added to a pool of views
* for later rebinding and reuse.
*
* A view must be fully detached (removed from parent) before it may be recycled. If the
* View is scrapped, it will be removed from scrap list.
*
* @param view Removed view for recycling
* @see LayoutManager#removeAndRecycleView(View, Recycler)
*/
public void recycleView(View view) {
// This public recycle method tries to make view recycle-able since layout manager
// intended to recycle this view (e.g. even if it is in scrap or change cache)
//根据传入的view获取相关的viewholder
ViewHolder holder = getChildViewHolderInt(view);
//如果这个holder已经被打上了清除的标记,则将其移除
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
//如果这个holder是来自缓存的可见viewholder数组,将其移除
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
//如果这个holder是来自缓存的不可见viewholder数组,将其移除
holder.clearReturnedFromScrapFlag();
}
//开始缓存
recycleViewHolderInternal(holder);
}
该方法首先根据view获取holder,然后判断holder是否已经在缓存中,如果在,将其从缓存中清除,最后调用recycleViewHolderInternal方法开始缓存。
/**
* internal implementation checks if view is scrapped or attached and throws an exception
* if so.
* Public version un-scraps before calling recycle.
*/
void recycleViewHolderInternal(ViewHolder holder) {
//代码省略(这里主要是对holder进行了一些判断)......
if (forceRecycle || holder.isRecyclable()) {
//如果当前有缓存,且缓存的数量大于0,并且viewholder的flag是有效的并且不是REMOVED和UPDATE,进行缓存
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
//移除缓存中的第一个view
recycleCachedViewAt(0);
cachedViewSize--;
}
//获取要缓存的holder在缓存集合中的position
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
// when adding the view, skip past most recently prefetched views
//缓存的时候,不能覆盖最近经常使用到的缓存
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
targetCacheIndex = cacheIndex + 1;
}
//添加缓存
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
//如果没有缓存的话,则添加到RecyclerViewPool中
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are still removing it from animation lists"
+ exceptionLabel());
}
}
// even if the holder is not removed, we still call this method so that it is removed
// from view holder lists.
//从mViewInfoStore中移除这个holder
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
在这里可以看出,RecyclerView的存缓存策略使用的最少使用策略。当我们存储viewholder的时候,会判断这个viewholder是否来自缓存,如果是的话,那么在存缓存的时候就不能覆盖最近比较频繁使用的缓存。
如何判断这个viewholder缓存最近是否频繁使用呢?官方定义了一个辅助类--GapWorker,它有一个内部类LayoutPrefetchRegistryImpl,然后在这个内部类有一个成员数组int[] mPrefetchArray
,这个数组是用来记录最近使用过的holder,所以RecyclerView在存缓存的时候会将要保存的viewholder与这个数组里面的viewholder进行匹配,代码如下:
boolean lastPrefetchIncludedPosition(int position) {
if (mPrefetchArray != null) {
final int count = mCount * 2;
for (int i = 0; i < count; i += 2) {
//如果viewholder是最近使用的viewholder,则返回true
if (mPrefetchArray[i] == position) return true;
}
}
return false;
}
recycleAndClearCachedViews:将CacheViews中的ViewHolder添加进RecyclerViewHolder,然后清空CacheViews。
void recycleAndClearCachedViews() {
final int count = mCachedViews.size();
for (int i = count - 1; i >= 0; i--) {
//将mCachedViews中的viewholder存储到RecyclerViewPool中
recycleCachedViewAt(i);
}
//清空mCachedViews集合
mCachedViews.clear();
if (ALLOW_THREAD_GAP_WORK) {
mPrefetchRegistry.clearPrefetchPositions();
}
}
recycleCachedViewAt
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
//从mCachedViews中取出viewholder
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
//将viewholder添加到RecyclerViewPool中
addViewHolderToRecycledViewPool(viewHolder, true);
//从mCachedViews中移除这个viewholder
mCachedViews.remove(cachedViewIndex);
}
addViewHolderToRecycledViewPool
void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
ViewCompat.setAccessibilityDelegate(holder.itemView, null);
}
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
holder.mOwnerRecyclerView = null;
//将viewholder存储到RecyclerViewPool中
getRecycledViewPool().putRecycledView(holder);
}
参考文章:
【进阶】RecyclerView源码解析(二)——缓存机制
深入浅出 RecyclerView
Android中的缓存艺术,对比RecyclerView与ListView的缓存机制