RecyclerView缓存机制

RecyclerView的缓存的目的是复用指定position或者指定id对应的ViewHolder. 减少创建和绑定ViewHolder,预加载ViewHolder可以使滚动流畅,方便执行预定义的动画等。

mAttachedScrap : 屏幕内的缓存

AttachedScrap.png

图中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 。


mCacheView.png

第一阶段: 开启一个线程,依据预加载策略,加载一个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

缓存寻找过程.png

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中。

你可能感兴趣的:(RecyclerView缓存机制)