RecyclerView的缓存的目的是复用指定position或者指定id对应的ViewHolder. 减少创建和绑定ViewHolder,预加载ViewHolder可以使滚动流畅,方便执行预定义的动画等。
mAttachedScrap : 屏幕内的缓存
图中4:会把屏幕内的ViewHolder缓存。
图中7 : 会寻找ViewHolder 填充布局。第一次填充布局肯定是创建的ViewHolder。第二次肯定从mAttachScrap中或者RecyclerPool中找到。
图中8:布局addViewInt()之后,就remove清除mAttachScrp的缓存。
从调用流程可以看出,mAttachScrap缓存屏幕内的Viewholder, 之前layout完成,再次layout的时候,才会缓存屏幕中的viewHolder.
比如如下情况:
1、执行 requestlayout()。
2、 执行RecyclerView.dispatchLayout() , setAdapter()。
3、notifyItemChanged(index),holder必须是可见的。只有改变的holder需要绑定数据。
/**
* Helper method reflect data changes to the state.
*
* Adapter changes during a scroll may trigger a crash because scroll assumes no data change
* but data actually changed.
*
* This method consumes all deferred changes to avoid that case.
*/
void consumePendingUpdateOperations() {
if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
.hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
| AdapterHelper.UpdateOp.MOVE)) {
TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mAdapterHelper.preProcess();
if (!mLayoutWasDefered) {
//有更新操作,就会触发layout
if (hasUpdatedView()) {
dispatchLayout();
} else {
// no need to layout, clean state
mAdapterHelper.consumePostponedUpdates();
}
}
stopInterceptRequestLayout(true);
onExitLayoutOrScroll();
TraceCompat.endSection();
} else if (mAdapterHelper.hasPendingUpdates()) {
TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
}
}
4、notifyDataSetChanged() 会触发requetLayout,但是数据被标记成了FLAG_INVALID. 因此holder会被放在recyclerPool中。下次加载需要重新绑定数据。
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
//标记holder为无效的。后续detachAndScrapView会放在recyclerPool中
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
mCachedViews : 屏幕外缓存
缓存的Viewholder可以直接使用,不需要再绑定数据,默认缓存2个,可以自定义缓存大小。API大于等于21时,对于LinlayoutManager,最多缓存3个 = 最多2个屏幕外ViewHolder + 1个预读取ViewHolder 。
第一阶段: 开启一个线程,依据预加载策略,加载一个ViewHolder. 如果viewHolder有效,就加入到Cahche中,否则加入到RecyclerPool中。
private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
int position, long deadlineNs) {
RecyclerView.Recycler recycler = view.mRecycler;
RecyclerView.ViewHolder holder;
try {
view.onEnterLayoutOrScroll();
holder = recycler.tryGetViewHolderForPositionByDeadline(
position, false, deadlineNs);
if (holder != null) {
if (holder.isBound() && !holder.isInvalid()) {
//加入缓存。
recycler.recycleView(holder.itemView);
} else {
//加入recyclePool
recycler.addViewHolderToRecycledViewPool(holder, false);
}
}
} finally {
view.onExitLayoutOrScroll(false);
}
return holder;
}
第二阶段: 通过滑动的方向和距离,缓存离开屏幕的ViewHolder,刚滑出屏幕的ViewHolder如果数据有效,缓存在cache中,否则缓存在recyclerPool中,如果cache达到最大的size = 3个了,就删除第0个位置(或者非预加载)的ViewHolder
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
int scrollingOffset = layoutState.mScrollingOffset;
int noRecycleSpace = layoutState.mNoRecycleSpace;
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
}
private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
final int childCount = getChildCount();
final int limit = mOrientationHelper.getEnd() - scrollingOffset + noRecycleSpace;
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedStart(child) < limit
|| mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
recycleChildren(recycler, childCount - 1, i);
return;
}
}
}
第三阶段:获取一个ViewHolder,加入ViewHolder.itemView ,layoutwithmargin布局
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LinearLayoutManager.LayoutState layoutState, LinearLayoutManager.LayoutChunkResult result) {
View view = layoutState.next(recycler);
if (view == null) {
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LinearLayoutManager.LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LinearLayoutManager.LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LinearLayoutManager.LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LinearLayoutManager.LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
layoutDecoratedWithMargins(view, left, top, right, bottom);
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
RecycledViewPool : 缓存池。
1、需要重新绑定ViewHolder
2、数据无效,过时,position改变,id改变 的viewHolder都需要存入Pool中。cache中的ViewHolder超过最大size. 将移除第0个放入pool中。
3、数据结构。每种类型最多存放5个Viewholder.
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray mScrap = new SparseArray<>(); // key-value
}
4、从cacheScraps中移除加入到pool。无效,需要更新的,remove的直接加入到pool中。
void recycleViewHolderInternal(RecyclerView.ViewHolder holder) {
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
@SuppressWarnings("unchecked")
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(RecyclerView.ViewHolder.FLAG_INVALID
| RecyclerView.ViewHolder.FLAG_REMOVED
| RecyclerView.ViewHolder.FLAG_UPDATE
| RecyclerView.ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
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;
}
}
// 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;
}
}
ViewCacheExtension : 自定义缓存实现。
public abstract static class ViewCacheExtension {
@Nullable
public abstract View getViewForPositionAndType(@NonNull RecyclerView.Recycler recycler, int position,
int type);
}
寻找一个ViewHolder
1、onMeasure()中dispatchLayoutStep1();
2、mLayout.onLayoutChildren(mRecycler, mState);
3、 fill(recycler, mLayoutState, state, false);
4、layoutChunk(recycler, state, layoutState, layoutChunkResult);
5、recycler.getViewForPosition(mCurrentPosition); 如图一所示。
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
boolean fromScrapOrHiddenOrCache = false;
RecyclerView.ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(RecyclerView.ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
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
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final RecyclerView.LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (RecyclerView.LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (RecyclerView.LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (RecyclerView.LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
return holder;
}
mChangedScrap 数据改变的ViewHolder,用于动画
例如: notifyItemChanged(index)
会把改变得ViewHolder放在mChangeScrap中,当preLayout加载位置信息,layout的时候再创建或者从pool获取一个新的ViewHolder,绑定数据。然后执行完成动画。将old viewHolder
移除到pool中。