之前也从鸿洋大神那看过类似博文,留下的大概印象是:recyclerView有四级缓存,其中一种是自定义缓存,当时作者还尝试使用自定义的缓存,但是结果是缓存错乱,作者说暂时还不明确它的作用,目前官方为我们定义的其他的缓存已经够用了。
看完之后感觉有点乱,还是没能理解它的原理和机制。
我们知道缓存其实缓存的就是ViewHolder,可以通过打印查看onCreateViewHolder和onBindViewHolder来查看缓存情况。
四级缓存:
1. mChangeScrap与 mAttachedScrap 用来缓存还在屏幕内的 ViewHolder
2. mCachedViews 用来缓存移除屏幕之外的 ViewHolder
3. mViewCacheExtension 开发给用户的自定义扩展缓存,需要用户自己 管理 View 的创建和缓存
4. RecycledViewPool ViewHolder 缓存池
缓存就是在我们滑动的时候做的,所以我们直接从recyclerView的onTouchEvent的move事件跟踪查看
recyclerView的源码实在太多,无法一一分析,只能跟踪大概流程,知道它的原理即可。
滑动:move–>scrollByInternal–>scrollStep–>水平和垂直两种我们选一种看scrollVerticallyBy
–>我们找一个它的实现类LinearLayoutManager–》scrollBy–》fill(关键逻辑代码都在这里面了)–》layoutChunk–》layoutState.next–》addView
layoutState.next–》getViewForPosition–》tryGetViewHolderForPositionByDeadline(核心)–》
1、getChangedScrapViewForPosition 主要与动画相关,失败了就从下面2找
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
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;
}
2、getScrapOrHiddenOrCachedHolderForPosition–》获取:mAttachedScrap 、mCachedViews 如果失败了则从3找一个通过position一个通过viewType和itemId
3、getScrapOrCachedViewForId–》获取:mAttachedScrap 、mCachedViews
4、mViewCacheExtension .getViewForPositionAndType–》自定义缓存
5、getRecycledViewPool().getRecycledView–》从缓存池中获取
上面所有的判断都失败了,拿不到holder的时候则mAdapter.createViewHolder–》tryBindViewHolderByDeadline–》onBindViewHolder
多级缓存的目的–》性能
回收:布局刷新的时候会启动回收。从实际的操作中我们滑动recyclerView的时候,它是在不停地刷新,一边在回收,一边在复用。从onLayoutChildren开始看
开始:LinearLayoutManager.onLayoutChildren–》detachAndScrapAttachedViews–》scrapOrRecycleView–》
1、recycler.recycleViewHolderInternal(viewHolder);处理cacheView、recyclerViewPool
void recycleViewHolderInternal(ViewHolder holder) {
...
if (forceRecycle || holder.isRecyclable()) {
//mViewCacheMax默认 == 2, ViewHolder没有做出改变的时候进入这个判断
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();
//大于等于== 默认2的时候进入这个判断
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
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;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
// NOTE: A view can fail to be recycled when it is scrolled off while an animation
// runs. In this case, the item is eventually recycled by
// ItemAnimatorRestoreListener#onAnimationFinished.
// TODO: consider cancelling an animation when an item is removed scrollBy,
// to return it to the pool faster
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.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
当满足cachedViewSize >= mViewCacheMax && cachedViewSize > 0的时候–》
recycleCachedViewAt(0);
cachedViewSize–;
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
上面的代码非常简单,就是从cacheView里面拿0位置的缓存放到recycerViewPool里面
然后cacheView把0位置的删除掉,之后cachedViewSize–; (先进先出 队列)
这个if判断再之后的代码是将当前缓存放到cacheView里面
cacheView缓存的ViewHolder是怎么进行保存的:
public final View itemView;
WeakReference<RecyclerView> mNestedRecyclerView;
int mPosition = NO_POSITION;
int mOldPosition = NO_POSITION;
long mItemId = NO_ID;
int mItemViewType = INVALID_TYPE;
int mPreLayoutPosition = NO_POSITION;
...
可以看到有position和viewType,也佐证了上面找缓存通过position和viewType来找的原因,因为这里保存了对应的位置和类型
当上面的if判断://mViewCacheMax >= 2, 且ViewHolder没有做出改变的时候这个判断失败之后则–》addViewHolderToRecycledViewPool–》getRecycledViewPool().putRecycledView(holder);
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);
}
上面的代码逻辑非常简单,根据对应的viewType创建对应的ViewHolder进行保存
其中mMaxScrap是==5,也就是每一种viewType的最大数量只保存5个
到了这里cacheView和recyclerViewPool这两种缓存就看完了
2、recycler.scrapView(view);
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);
}
}
根据holder是否做出改变(包括标记、更新、动画、移除等判断)来确认是哪一种缓存
1、mAttachedScrap.add(holder); 没改变
2、mChangedScrap.add(holder);已经改变
在刷新布局的时候onlayout的时候会触发回收复用
回收入口:recyclerView.onLayout->dispatchLayout–>dispatchLayoutStep2->mLayout.onLayoutChildren(mRecycler, mState);–>fill(复用流程)
复用:fill–》recycleByLayoutState–》
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
上滑或者下滑
recycleViewsFromStart–》recycleChildren–》removeAndRecycleViewAt–》recycler.recycleView–》recycleViewHolderInternal–》缓存
在刷新的时候onLayout只会触发cacheView和recyclerViewPool这两种缓存
没有看到使用自定义缓存的地方,这貌似也是当初很早的时候recyclerView出来我看到的那篇博文,作者去使用自定义缓存出问题的原因。