RecyclerView 回收机制解析

概述

近期接触到RecyclerView回收机制相关的内容,于是作此文记录下相关探索。
如记录的有问题欢迎评论探讨。

定位回收机制源码

笔者使用debug的方式,在adapter的onCreateViewHolder中打断点,于是得到该任务栈。

思路:onCreateViewHolder是创建viewHolder的方法,那么在调用这个方法之前一定有是否复用的判断,因此断点在这个方法可以找到相关方法。

然后我们就可以发现是RecyclerView#Recycler的tryGetViewHolderForPositionByDeadline()方法中处理的相关的回收逻辑,源码如下:

        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
            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(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) 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) {
                            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());
                        }
                    }
                }
                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);
                        }
                    }
                }
                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");
                    }
                }
            }

            // 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);
                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;
            }
            rvLayoutParams.mViewHolder = holder;
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            return holder;
        }

回收机制

tryGetViewHolderForPositionByDeadline()源码中有几个获得ViewHodler的方式:

  1. holder = getChangedScrapViewForPosition(position);
  2. holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
  3. final View view = mViewCacheExtension
    .getViewForPositionAndType(this, position, type);
  4. holder = getRecycledViewPool().getRecycledView(type);
  5. holder = mAdapter.createViewHolder(RecyclerView.this, type);

笔者整理后,根据RecyclerView#Recycler的几个参数,按照代码顺序是如下几个回收模块:

  1. mAttachedScrap
  2. mChangedScrap
  3. mCachedViews
  4. mViewCacheExtension
  5. mRecyclerPool

mAttachedScrap与mChangedScrap

  • mAttachedScrap
    用于存储ViewHolder已经从RecyclerView上移除,但是仍有可能被复用的View。
  • mChangedScrap
    用于存储ViewHolder仍在RecyclerView上,但是数据已经过时,需要被更新的View。

针对这两种场景,笔者特别做了debug实验:

  • mChangedScrap:
    场景一:通过adapter设置notifyItemChanged(index),如果当前index显示在屏幕中,这个index的ViewHodler会被存储到mChangedScrap中。
  • mAttachedScrap:
    场景一:让一个RecyclerView 隐藏后再显示,当前页面的中的item都会存储到mAttachedScrap中。
    场景二:通过adapter设置notifyItemChanged(index),当前屏幕中index以外的其他item都会被存储到mAttachedScrap中。

源码解析

在源码中查找mAttachedScrap与mChangedScrap填充内容的地方,可以找到是在同一处。

        /**
         * Mark an attached view as scrap.
         *
         * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible
         * for rebinding and reuse. Requests for a view for a given position may return a
         * reused or rebound scrap view instance.</p>
         *
         * @param view View to scrap
         */
        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);
            }
        }
        

通过上述的注释可知,这两个list都是用来存储不需要的view,然后后面准备复用的。
从源码中可以看到,需要ViewHolder本身没有失效(postsition,id,viewType都有效),否则会报异常。
另外的,两个list分别会存储ViewHolder的场景如下。

mAttachedScrap

由源码if-else中可知,mAttachedScrap有三种场景会进入:

  1. ViewHolder有ViewHolder.FLAG_REMOVED
  2. ViewHolder中没有ViewHolder.FLAG_REMOVED和ViewHolder.FLAG_UPDATE
  3. ViewHolder中没有ViewHolder.FLAG_REMOVED,但是有ViewHolder.FLAG_UPDATE,并且canReuseUpdatedViewHolder(holder)返回true

mChangedScrap

其他的场景都会存储到mChangedScrap中,需要同时满足这几个条件:

  1. ViewHolder中没有ViewHolder.FLAG_REMOVED
  2. ViewHolder有ViewHolder.FLAG_UPDATE
  3. canReuseUpdatedViewHolder(holder)返回false

ViewHolder的几个Flag

根据上面的面源码,根据if-else里的判断条件,可以看到两个的区别首先在三个flag上,FLAG_REMOVED,FLAG_INVALID,FLAG_UPDATE。
这三个flag的源码和注释如下:

        /**
         * The data this ViewHolder's view reflects is stale and needs to be rebound
         * by the adapter. mPosition and mItemId are consistent.
         */
        static final int FLAG_UPDATE = 1 << 1;
        
                /**
         * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
         * are not to be trusted and may no longer match the item view type.
         * This ViewHolder must be fully rebound to different data.
         */
        static final int FLAG_INVALID = 1 << 2;

        /**
         * This ViewHolder points at data that represents an item previously removed from the
         * data set. Its view may still be used for things like outgoing animations.
         */
        static final int FLAG_REMOVED = 1 << 3;

翻译后,三个flag的描述大概如下:

  • FLAG_UPDATE
    这个ViewHolder需要通过adapter来bind内容。这个ViewHolder的postion和id是统一的,有效的。
  • FLAG_REMOVED
    ViewHolder已经过时了,但是这个view仍可能被复用,比如用在跳出的动画。
  • FLAG_INVALID
    这个ViewHolder中的数据已经无效了。也无法通过postion或者id来识别这个ViewHolder(position和id无效了),也不会与这个item的viewType匹配。
    这个ViewHolder会完全与其他的数据绑定。

canReuseUpdatedViewHolder

    boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
        return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
                viewHolder.getUnmodifiedPayloads());
    }
    
    
/**
         * When an item is changed, ItemAnimator can decide whether it wants to re-use
         * the same ViewHolder for animations or RecyclerView should create a copy of the
         * item and ItemAnimator will use both to run the animation (e.g. cross-fade).
         * <p>
         * Note that this method will only be called if the {@link ViewHolder} still has the same
         * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
         * both {@link ViewHolder}s in the
         * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
         *
         * @param viewHolder The ViewHolder which represents the changed item's old content.
         * @param payloads A non-null list of merged payloads that were sent with change
         *                 notifications. Can be empty if the adapter is invalidated via
         *                 {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of
         *                 payloads will be passed into
         *                 {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)}
         *                 method <b>if</b> this method returns <code>true</code>.
         *
         * @return True if RecyclerView should just rebind to the same ViewHolder or false if
         *         RecyclerView should create a new ViewHolder and pass this ViewHolder to the
         *         ItemAnimator to animate. Default implementation calls
         *         {@link #canReuseUpdatedViewHolder(ViewHolder)}.
         *
         * @see #canReuseUpdatedViewHolder(ViewHolder)
         */
        public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
                @NonNull List<Object> payloads) {
            return canReuseUpdatedViewHolder(viewHolder);
        }

根据源码可知,这个方法的作用如下:

  1. 首先判断item有无动画,如果没有动画直接返回true。
  2. 判断item的动画能否复用ViewHolder来播放动画,还是需要新建一个ViewHolder。

mCachedViews

用于缓存滑出屏幕的ViewHodler。
缓存的个数,由RecyclerView#Recycler#mViewCacheMax决定,可以通过RecyclerView#setItemViewCacheSize来设置。

场景
加入此时屏幕中有10个Item,当用户上滑一个,内容下移一个时,原先第一个的Item中的ViewHodler会被加入到mCachedViews中。

源码解析

首先直接找到在mCachedViews中缓存的位置。

       /**
         * 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) {
            if (holder.isScrap() || holder.itemView.getParent() != null) {
                throw new IllegalArgumentException(
                        "Scrapped or attached views may not be recycled. isScrap:"
                                + holder.isScrap() + " isAttached:"
                                + (holder.itemView.getParent() != null) + exceptionLabel());
            }

            if (holder.isTmpDetached()) {
                throw new IllegalArgumentException("Tmp detached view should be removed "
                        + "from RecyclerView before it can be recycled: " + holder
                        + exceptionLabel());
            }

            if (holder.shouldIgnore()) {
                throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
                        + " should first call stopIgnoringView(view) before calling recycle."
                        + exceptionLabel());
            }
            //noinspection unchecked
            final boolean transientStatePreventsRecycling = holder
                    .doesTransientStatePreventRecycling();
            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()) {
                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) {
                        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;
            }
        }
        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);
        }

此方法逻辑如下:

  1. 首先判断ViewHolder是否可以缓存
  2. 如果mCachedViews的size已经大于等于mViewCacheMax,那么会将超index=0这个位置的ViewHolder放入到回收池中。
  3. 此时mCachedViews的size不会大于mViewCacheMax,将ViewHodler添加到mCachedViews中

mViewCacheExtension

直接看源码。

    /**
     * ViewCacheExtension is a helper class to provide an additional layer of view caching that can
     * be controlled by the developer.
     * <p>
     * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
     * first level cache to find a matching View. If it cannot find a suitable View, Recycler will
     * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
     * {@link RecycledViewPool}.
     * <p>
     * Note that, Recycler never sends Views to this method to be cached. It is developers
     * responsibility to decide whether they want to keep their Views in this custom cache or let
     * the default recycling policy handle it.
     */
    public abstract static class ViewCacheExtension {

        /**
         * Returns a View that can be binded to the given Adapter position.
         * <p>
         * This method should <b>not</b> create a new View. Instead, it is expected to return
         * an already created View that can be re-used for the given type and position.
         * If the View is marked as ignored, it should first call
         * {@link LayoutManager#stopIgnoringView(View)} before returning the View.
         * <p>
         * RecyclerView will re-bind the returned View to the position if necessary.
         *
         * @param recycler The Recycler that can be used to bind the View
         * @param position The adapter position
         * @param type     The type of the View, defined by adapter
         * @return A View that is bound to the given position or NULL if there is no View to re-use
         * @see LayoutManager#ignoreView(View)
         */
        @Nullable
        public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
                int type);
    }

ViewCacheExtension是提供给开发者自定义View缓存的一个帮助类。
如果使用ViewCacheExtension来自定义缓存,其内容与RecyclerView#Recycler是无关的,需要开发者自己定义View的缓存与回收逻辑。

RecycledViewPool

RecycledViewPool能够共享多个RecyclerView之间的View。(同一个Adapter)
默认情况下每个RecyclerView会自己创建一个RecycledViewPool,开发者如果需要多个RecycledView复用一个Pool可以通过RecycledView#setRecycledViewPool()来设置。

场景:当用户上下滑动页面触发回收ViewHolder逻辑,并且mCachedViews中已经存放满了的时候,会放到mRecyclerPool中.

存储结构

        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<>();  
        
  • ViewHolder存储与RecycledViewPool的ScapData数据结构的ArrayList中。
  • ScrapData则存储在RecycledViewPool中的SparseArray中。
  • ScrapData中的ArrayList默认的存储上限是5.

获取ViewHolder

        /**
         * Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are
         * present.
         *
         * @param viewType ViewHolder type.
         * @return ViewHolder of the specified type acquired from the pool, or {@code null} if none
         * are present.
         */
        @Nullable
        public ViewHolder getRecycledView(int viewType) {
            final ScrapData scrapData = mScrap.get(viewType);
            if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
                final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
                return scrapHeap.remove(scrapHeap.size() - 1);
            }
            return null;
        }
  1. 根据viewType去SparseArray中获取到ScrapData。
  2. 从ScrapData的ArrayList中返回一个ViewHodler出去,并且从该mScrapHeap中移除。

回收ViewHolder

        /**
         * Add a scrap ViewHolder to the pool.
         * <p>
         * 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) {
            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);
        }
        void resetInternal() {
            mFlags = 0;
            mPosition = NO_POSITION;
            mOldPosition = NO_POSITION;
            mItemId = NO_ID;
            mPreLayoutPosition = NO_POSITION;
            mIsRecyclableCount = 0;
            mShadowedHolder = null;
            mShadowingHolder = null;
            clearPayload();
            mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
            mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
            clearNestedRecyclerViewIfNotNested(this);
        }
  1. 先获取到ViewHolder的viewType,然后根据viewType从SparseArray中获取到ScrapData
  2. 如果ScrapData中的ArrayList已经到达上限,那么久直接返回,不回收。
  3. 如果ScrapData中的ArrayList已经包含了该ViewHolder,那么直接报错。
  4. 在ViewHolder放入到回收池之前清除数据。
  5. 将该ViewHolder放入到ScrapData的ArrayList中。

总结

RecyclerView的回收机制中主要是以下几个模块:

模块描述场景
mAttachedScrap用于存储ViewHolder已经从RecyclerView上移除,但是仍有可能被复用的View。通过adapter设置notifyItemChanged(index),当前屏幕中index以外的其他item都会被存储到mAttachedScrap中。
mChangedScrap用于存储ViewHolder仍在RecyclerView上,但是数据已经过时,需要被更新的View。通过adapter设置notifyItemChanged(index),如果当前index显示在屏幕中,这个index的ViewHodler会被存储到mChangedScrap中。
mCachedViews用于缓存滑出屏幕的ViewHodler。当mCachedViews满了之后,会把最老的ViewHolder的放入到RecyclerViewPool。加入此时屏幕中有10个Item,当用户上滑一个,内容下移一个时,原先第一个的Item中的ViewHodler会被加入到mCachedViews中。
mViewCacheExtensionViewCacheExtension是提供给开发者自定义View缓存的一个帮助类。如果使用ViewCacheExtension来自定义缓存,需要开发者自己定义View的缓存与回收逻辑。不常用,开发者可自由定义。
mRecyclerPoolRecycledViewPool能够共享多个RecyclerView之间的View。(同一个Adapter)默认情况下每个RecyclerView会自己创建一个RecycledViewPool,开发者如果需要多个RecycledView复用一个Pool可以通过RecycledView#setRecycledViewPool()来设置。屏幕上下滑动缓存ViewHolder,且mCachedViews中放满的情况下会放到RecycledViewPool中

注:

  • 与当前的RecyclerView一一对应:
    mAttachedScrap、mChangedScrap、mCachedViews
  • 可能多个RecyclerView共享:
    mViewCacheExtension、mRecyclerPool
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值