RecyclerView源码分析(二):RecyclerView的缓存与复用机制

1、开篇

在上一篇文章说到,LineaLayoutManager在填充布局之前会先调用detachAndScrapAttachedViews方法先暂时回收子View,然后在layoutChunk中进行子View的获取(可能会创建View)、测量、布局以及回收流程。实际上三大LayoutManager的流程都是一样的,只是细节上有所差异,感兴趣的同学可以阅读一下源码看看。本篇中涉及LayoutManager的部分依旧会以LinearLayoutManager为例。
回顾一下本系列要解决的问题:

  1. 既然是个ViewGroup,那少不了要问上一句:它的measure、layout和draw是怎么样的?
  2. RecyclerView是怎么回收View的?什么时候回收?
  3. 怎么支持多类型Item的?怎么缓存和查找的呢?
  4. Adapter的onRecreateViewHolder和onBindViewHolder两大核心方法是什么时候调用的?
  5. Item动画过程中notifyXXXChange会不会导致动画的错位?

问题1上篇文章已经解决了,而本篇要解决其中的234。

2、LayoutManager#detachAndScrapAttachedViews

detachAndScrapAttachedViews是基类LayoutManager定义的方法,如下所示:

public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
    final int childCount = getChildCount();
    for (int i = childCount - 1; i >= 0; i--) {
        final View v = getChildAt(i);
        scrapOrRecycleView(recycler, i, v);
    }
}

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    // shouldIgnore()为true表示ViewHolder不应该被回收
    if (viewHolder.shouldIgnore()) {
        if (DEBUG) {
            Log.d(TAG, "ignoring view " + viewHolder);
        }
        return;
    }

    // isInvalid()为true表示Item的数据发生变化了(notifyDataSetChanged),需要重新绑定
    // isRemoved()为true表示Item被移除了
    // hasStableIds()为true表示每个Item会拥有一个唯一的long类型的id
    // 我们可以通过Adapter.setHasStableIds(true)并重写Adapter.getItemId(int)方法来指定每个Item的id
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        removeViewAt(index);
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        detachViewAt(index);
        recycler.scrapView(view);
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}

detachAndScrapAttachedViews遍历了子View,挨个调用了scrapOrRecycleView方法进行回收。scrapOrRecycleView方法对不同情况的View进行了不同的回收处理:

  • viewHolder.shouldIgnore()为true:不回收
  • 数据发生变化,但是Item没有被移除且没有指定ItemId
  • 其他情况

2.1 数据发生变化,但是Item没有被移除且没有指定ItemId

不回收的情况就不用多说了,来看一下第二种情况。先看removeViewAt(index)方法

LayoutManager#removeViewAt:
public void removeViewAt(int index) {
    final View child = getChildAt(index);
    if (child != null) {
        mChildHelper.removeViewAt(index);
    }
}


ChildHelper#removeViewAt:
void removeViewAt(int index) {
    final int offset = getOffset(index);
    final View view = mCallback.getChildAt(offset);
    if (view == null) {
        return;
    }
    if (mBucket.remove(offset)) {
        unhideViewInternal(view);
    }
    mCallback.removeViewAt(offset);
    if (DEBUG) {
        Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this);
    }
}

可以看到,最终是调用了ChildHelper的mCallback.removeViewAt方法。这个mCallback是ChildHelper的构造方法传入的,它的实例化在RecyclerView#initChildrenHelper中:

private void initChildrenHelper() {
    mChildHelper = new ChildHelper(new ChildHelper.Callback() {
       ...

        @Override
        public void removeViewAt(int index) {
            final View child = RecyclerView.this.getChildAt(index);
            if (child != null) {
                dispatchChildDetached(child);

                // Clear any android.view.animation.Animation that may prevent the item from
                // detaching when being removed. If a child is re-added before the
                // lazy detach occurs, it will receive invalid attach/detach sequencing.
                // 清除View的动画
                child.clearAnimation();
            }
            
            RecyclerView.this.removeViewAt(index);
            
        }

        ...
    });
}

所以在这种情况下,子View真的从RecyclerView中移除了。那么再来看Recycler的recycleViewHolderInternal方法对这个View已经被移除的ViewHolder做了些什么事情

void recycleViewHolderInternal(ViewHolder holder) {
    // 对holder进行各种状态检查,这段代码省略
    ...

    // 强制回收判断
    // transientStatePreventsRecycling如果是true代表View处于某种临时状态(执行动画等)不能被回收
    // 这时候可以通过重写Adapter.onFailedToRecycleView方法来解除这个临时状态并返回true以达到可以被回收的目的
    final boolean transientStatePreventsRecycling = holder
            .doesTransientStatePreventRecycling();
    @SuppressWarnings("unchecked")
    final boolean forceRecycle = mAdapter != null
            && transientStatePreventsRecycling
            && mAdapter.onFailedToRecycleView(holder);
    boolean cached = false;
    boolean recycled = false;
    if (DEBUG && mCachedViews.contains(holder)) {
        throw new IllegalArgumentException("cached view received recycle internal? "
                + holder + exceptionLabel());
    }
    if (forceRecycle || holder.isRecyclable()) { // 强制回收或者holder是可回收的
        if (mViewCacheMax > 0
                && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                | ViewHolder.FLAG_REMOVED
                | ViewHolder.FLAG_UPDATE
                | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { // 注意这里的flag,没有这些flag的才会走接下来的回收流程
            // 从这里可以看出mViewCacheMax是mCachedViews的存储数量上限
            // 如果达到了这个上限,先移除最老的
            int cachedViewSize = mCachedViews.size();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                recycleCachedViewAt(0);
                cachedViewSize--;
            }

            int targetCacheIndex = cachedViewSize;
            ... // 省略targetCacheIndex计算逻辑
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
        // 注意:滚动时,如果View在执行动画的过程中可能会回收失败
        // 这种情况下,ItemAnimatorRestoreListener#onAnimationFinished回调中回收此View
        // 请考虑取消滚动出去时取消View的动画,以使View更快地被回收到回收池
        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.
    // 即使holder没有被移除/回收,也从list中先行移除
    mViewInfoStore.removeViewHolder(holder);
    if (!cached && !recycled && transientStatePreventsRecycling) {
        holder.mOwnerRecyclerView = null;
    }
}

void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
    ...
    if (dispatchRecycled) {
        dispatchViewRecycled(holder);
    }
    holder.mOwnerRecyclerView = null;
    getRecycledViewPool().putRecycledView(holder);
}

这里可以看出,mViewCacheMax<=0或者有以下几个flag之一的情况是不会进行holder的回收的

ViewHolder.FLAG_INVALID
ViewHolder.FLAG_REMOVED
ViewHolder.FLAG_UPDATE
ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN

回收的时候,会优先回收到mCachedViews中,mCachedViews是一个ViewHolder的ArrayList。而RecycledViewPool是一个比较简单的对象。注意,我们当前情况是有FLAG_INVALID这个flag的,所以就当前情况是不会回收到mCachedViews中的。

public static class RecycledViewPool {
    private static final int DEFAULT_MAX_SCRAP = 5;
    static class ScrapData {
        final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
        int mMaxScrap = DEFAULT_MAX_SCRAP;
        long mCreateRunningAverageNs = 0;
        long mBindRunningAverageNs = 0;
    }
    SparseArray<ScrapData> mScrap = new SparseArray<>();

    public ViewHolder getRecycledView(int viewType) {
        final ScrapData scrapData = mScrap.get(viewType);
        if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
            for (int i = scrapHeap.size() - 1; i >= 0; i--) {
                if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
                    return scrapHeap.remove(i);
                }
            }
        }
        return null;
    }

    ...

    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);
    }
    ...
}

其中ScrapData简单封装了一个ArrayList,而RecycledViewPool封装了一个SparseArray,key是viewType,value是ScrapData。也就是说,在RecyclerViewPool里面,ViewHolder是按照viewType分门别类地存储的。

2.2 其他情况

其他情况调用如下:

LayoutManager#scrapOrRecycleView:
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    ...
    else {
        detachViewAt(index);
        recycler.scrapView(view);
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}

先看LayoutManager的detachViewAt

public void detachViewAt(int index) {
    detachViewInternal(index, getChildAt(index));
}

private void detachViewInternal(int index, @NonNull View view) {
    if (DISPATCH_TEMP_DETACH) {
        ViewCompat.dispatchStartTemporaryDetach(view);
    }
    mChildHelper.detachViewFromParent(index);
}

mChildHelper最终其实也是调用了mCallback的相关回调


private void initChildrenHelper() {
    mChildHelper = new ChildHelper(new ChildHelper.Callback() {
        ...

        @Override
        public void detachViewFromParent(int offset) {
            final View view = getChildAt(offset);
            if (view != null) {
                final ViewHolder vh = getChildViewHolderInt(view);
                if (vh != null) {
                    ...
                    // 加一个flag: FLAG_TMP_DETACHED
                    vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
                }
            }
            RecyclerView.this.detachViewFromParent(offset);
        }

        ...
    });
}

最终调用了RecyclerView的detachViewFromParent。

再看Recycler的scrapView

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        ...
        holder.setScrapContainer(this, false);
        mAttachedScrap.add(holder);
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        mChangedScrap.add(holder);
    }
}

boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
    return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
            viewHolder.getUnmodifiedPayloads());
}

被标记为具有FLAG_REMOVED或者FLAG_INVALID、或者没有更新的以及可重用的ViewHolder被添加到了mAttachedScrap中,其余的被添加到了mChangedScrap中。这两个变量都是存放ViewHolder的ArrayList。

2.4 总结

现在来总结一下LayoutManager#detachAndScrapAttachedViews中的主要工作:

  1. 如果ViewHolder.shouldIgnore()返回true,忽略
  2. 如果ViewHolder.isInvalid()返回true且没有被移除也没有有效的id的时候,优先考虑缓存到mCachedViews里,mCachedViews达到设定的上限后,老的ViewHolder缓存到RecycledViewPool
  3. 其余情况,被标记为具有FLAG_REMOVED或者FLAG_INVALID、或者没有更新的以及可重用的ViewHolder被添加到了mAttachedScrap中,其余的被添加到了mChangedScrap中

3、LayoutState#next

看一下LinearLayoutManager.LayoutState的next方法

View next(RecyclerView.Recycler recycler) {
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

mScrapList是在layoutForPredictiveAnimations中被赋值的,由于layoutForPredictiveAnimations的执行在fill之后,这里我们姑且把mScrapList当作null,尽管它不一定总是null,主要看怎么从recycler中取出对应的View的。

Recycler中的相关方法:

public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    if (position < 0 || position >= mState.getItemCount()) {
        throw new IndexOutOfBoundsException("Invalid item position " + position
                + "(" + position + "). Item count:" + mState.getItemCount()
                + exceptionLabel());
    }
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    // 0) If there is a changed scrap, try to find from there
    // 预布局的时候从mChangedScrap中查找ViewHolder
    if (mState.isPreLayout()) { // 预布局
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    // 1) Find by position from scrap/hidden list/cache
    // 1)先尝试通过position从mAttachedScrap、mChildHeppler的hidden列表和mCachedViews里查找
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if (holder != null) {
            if (!validateViewHolderForOffsetPosition(holder)) {
                // 不能复用了,回收这个holder
                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);
                    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);
        if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
            throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                    + "position " + position + "(offset:" + offsetPosition + ")."
                    + "state:" + mState.getItemCount() + exceptionLabel());
        }

        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) 在通过id查找mAttachedScrap、mChildHeppler的hidden列表和mCachedViews
        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.
            // 从mViewCacheExtension查找
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                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());
                }
            }
        }

        // 最后才从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);
                }
            }
        }

        // 经过n此查找依然没有找到合适的ViewHolder,才通过Adapter创建新的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做了保存
                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");
            }
        }
    }

    // This is very ugly but the only place we can grab this information
    // before the View is rebound and returned to the LayoutManager for post layout ops.
    // We don't need this in pre-layout since the VH is not updated by the LM.
    if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
            .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
        holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
        if (mState.mRunSimpleAnimations) {
            int changeFlags = ItemAnimator
                    .buildAdapterChangeFlagsForAnimations(holder);
            changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
            final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                    holder, changeFlags, holder.getUnmodifiedPayloads());
            recordAnimationInfoIfBouncedHiddenView(holder, info);
        }
    }

    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        // do not update unless we absolutely have to.
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        if (DEBUG && holder.isRemoved()) {
            throw new IllegalStateException("Removed holder should be bound and it should"
                    + " come here only in pre-layout. Holder: " + holder
                    + exceptionLabel());
        }
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);

        // 尝试绑定ViewHolder
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }

    final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
    final LayoutParams rvLayoutParams;
    if (lp == null) {
        rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else if (!checkLayoutParams(lp)) {
        rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else {
        rvLayoutParams = (LayoutParams) lp;
    }

    // 这里itemView通过持有LayoutParams间接持有了ViewHolder
    // 这样我们可以通过itemView找到对应的ViewHolder,也可以通过ViewHolder找到itemView
    rvLayoutParams.mViewHolder = holder;
    rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
    return holder;
}

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;
    }
    
    // 先通过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;
        }
    }
    // 通过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;
}

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())) {
            // 标记FLAG_RETURNED_FROM_SCRAP
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }

    if (!dryRun) { // 根绝上面的代码,dryRun为false
        // 这些被标记为hidden的View存储在ChildHelper中的mHiddenViews里
        // 这些View所谓的hidden并不是指View的visibility,而是它们不能被某些常规方法访问
        // 这里我们不深究
        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.
            final ViewHolder vh = getChildViewHolderInt(view);
            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);
            scrapView(view);
            // 标记FLAG_RETURNED_FROM_SCRAP和FLAG_BOUNCED_FROM_HIDDEN_LIST
            vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                    | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
            return vh;
        }
    }

    // 从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
        if (!holder.isInvalid() && holder.getLayoutPosition() == position
                && !holder.isAttachedToTransitionOverlay()) {
            if (!dryRun) {
                mCachedViews.remove(i);
            }
            if (DEBUG) {
                Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                        + ") found match in cache: " + holder);
            }
            return holder;
        }
    }
    return null;
}

ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
    // 依然是先查找mAttachedScrap
    final int count = mAttachedScrap.size();
    for (int i = count - 1; i >= 0; i--) {
        final ViewHolder holder = mAttachedScrap.get(i);
        if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
            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.
                    // 这里可能会是以下两种情况之一:
                    // item被移除了,但是当前是预布局
                    // item被移除后又被添加到了其他的位置
                    if (!mState.isPreLayout()) {
                        holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
                                | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
                    }
                }
                return holder;
            } else if (!dryRun) {
                // 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.
                // 如果当前正在执行动画,保留在mAttachedScrap更好,但是这会导致LayoutManager会对它进行布局,这又不太好
                // 回收掉,因为viewType变了
                mAttachedScrap.remove(i);
                removeDetachedView(holder.itemView, false);
                quickRecycleScrapView(holder.itemView);
            }
        }
    }

    // 从mCachedViews查找
    final int cacheSize = mCachedViews.size();
    for (int i = cacheSize - 1; i >= 0; i--) {
        final ViewHolder holder = mCachedViews.get(i);
        if (holder.getItemId() == id && !holder.isAttachedToTransitionOverlay()) {
            if (type == holder.getItemViewType()) {
                if (!dryRun) {
                    mCachedViews.remove(i);
                }
                return holder;
            } else if (!dryRun) {
                recycleCachedViewAt(i);
                return null;
            }
        }
    }
    return null;
}

private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
        int position, long deadlineNs) {
    holder.mOwnerRecyclerView = RecyclerView.this;
    final int viewType = holder.getItemViewType();
    long startBindNs = getNanoTime();
    if (deadlineNs != FOREVER_NS
            && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
        // abort - we have a deadline we can't meet
        return false;
    }
    // 绑定ViewHolder
    mAdapter.bindViewHolder(holder, offsetPosition);
    long endBindNs = getNanoTime();
    mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
    attachAccessibilityDelegateOnBind(holder);
    if (mState.isPreLayout()) {
        holder.mPreLayoutPosition = position;
    }
    return true;
}

这段代码比较长,希望看到这里的同学耐心看完。总结一下要点:

  1. 预布局的时候,优先查找mChangedScrap,先position后id
  2. 先尝试通过position从mAttachedScrap、mChildHeppler的hidden列表和mCachedViews里查找ViewHolder
  3. 通过id从mAttachedScrap和mCachedViews里查找ViewHolder
  4. 从mViewCacheExtension查找ViewHolder
  5. 通过type从RecycledViewPool中查找ViewHolder
  6. 如果上述步骤都没有找到合适的ViewHolder,同过Adapter创建一个ViewHolder
  7. 根据需要绑定ViewHolder,只有needsUpdate()或者isInvalid()为true的时候才会执行ViewHolder的绑定,出现的情况有:Adapter的notifyItemChanged/notifyRangeChanged、notifyDatasetChanged相关方法以及新建ViewHolder或者从RecycledViewPool中取出ViewHolder

4、ViewHolder的回收

ViewHolder的回收(或者说缓存)有好几个地方,第一个就是上面分析的填充之前的LayoutManager#detachAndScrapAttachedViews,第二第三次分别在LinearLayoutManager#fill和RecyclerView#dispatchLayoutStep3中

先来看LinearLayoutManager#fill:

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    // max offset we should set is mFastScroll + available
    final int start = layoutState.mAvailable;
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        // TODO ugly bug fix. should not happen
        if (layoutState.mAvailable < 0) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        recycleByLayoutState(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        ...
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        ...

        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        if (stopOnFocusable && layoutChunkResult.mFocusable) {
            break;
        }
    }
    if (DEBUG) {
        validateChildOrder();
    }
    return start - layoutState.mAvailable;
}

这里有两段相似的代码,一段位于开头,一段位于while循环里,根据注释,开头这一段是“丑陋的bug修复代码”,所以主要发挥作用的应该是后者。它们都是调用了recycleByLayoutState(recycler, layoutState)来回收超出限制范围的View

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);
    }
}

从布局方向的反方向回收,这里最终经过层层嵌套还是调用了Recycler#recycleViewHolderInternal,这个方法前面已经分析过了就不再赘述了。

再来看RecyclerView#dispatchLayoutStep3

private void dispatchLayoutStep3() {
    mState.assertLayoutStep(State.STEP_ANIMATIONS);
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.mLayoutStep = State.STEP_START;
    ...

    mLayout.removeAndRecycleScrapInt(mRecycler);
    ...
}

这里调用了LayoutManager的removeAndRecycleScrapInt方法

void removeAndRecycleScrapInt(Recycler recycler) {
    final int scrapCount = recycler.getScrapCount();
    // Loop backward, recycler might be changed by removeDetachedView()
    for (int i = scrapCount - 1; i >= 0; i--) {
        final View scrap = recycler.getScrapViewAt(i);
        final ViewHolder vh = getChildViewHolderInt(scrap);
        if (vh.shouldIgnore()) {
            continue;
        }
        // If the scrap view is animating, we need to cancel them first. If we cancel it
        // here, ItemAnimator callback may recycle it which will cause double recycling.
        // To avoid this, we mark it as not recycleable before calling the item animator.
        // Since removeDetachedView calls a user API, a common mistake (ending animations on
        // the view) may recycle it too, so we guard it before we call user APIs.
        vh.setIsRecyclable(false);
        if (vh.isTmpDetached()) {
            mRecyclerView.removeDetachedView(scrap, false);
        }
        if (mRecyclerView.mItemAnimator != null) {
            mRecyclerView.mItemAnimator.endAnimation(vh);
        }
        vh.setIsRecyclable(true);

        // 最终也是调用recycleViewHolderInternal(holder)
        recycler.quickRecycleScrapView(scrap);
    }
    recycler.clearScrap();
    if (scrapCount > 0) {
        mRecyclerView.invalidate();
    }
}

这里遍历了Recycler的mAttachedScrap,挨个回收,随后清空了mAttachedScrap和mChangedScrap,确保RecyclerView中没有多余的View。

5、总结

RecyclerView的缓存复用类Recycler有四级缓存机制:

  1. mAttachedScrap和mChangedScrap,用于临时存储可能还会停留在RecyclerView中的ViewHolder,在dispatchLayoutStep3中多余的ViewHolder会被缓存到mCachedViews或者RecycledViewPool
  2. mCachedViews,缓存上限是mViewCacheMax,默认值为2,缓存最后被回收的ViewHolder
  3. mViewCacheExtension,开发者自定义的缓存,Recycler本身并不会往里面put数据,只检索,其他逻辑由开发者实现
  4. RecycledViewPool,按照分类存储ViewHolder,进入这里的ViewHolder的position等都被重置了

另外,开头的234问题,到此也有了答案~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值