RecyclerView源码学习笔记二(缓存机制)

在本文我们继续学习RecyclerView缓存的相关知识。
缓存分为缓存取出和缓存存入,首先来分析下缓存取出:

一、缓存取出

在上一篇文章中,我们分析到了一个比较关键的方法--layoutChunk,再来看下其源码:

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                 LayoutState layoutState, LayoutChunkResult result) {
    //获取view
    View view = layoutState.next(recycler);
    //代码省略......
}

这里只保留了一行很重要的代码View view = layoutState.next(recycler);,看下next方法。

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

这个next方法就是从缓存中来获取view,首先是判断mScrapList是否为空,不为空就调用nextViewFromScrapList();方法。这里是以LinearLayoutManager来分析的,mScrapList是LinearLayoutManager的一个成员,它主要与动画相关,因此这里就不再过多的分析它了。接着往下看,调用了Recycler的getViewForPosition(int position)方法,Recycler是RecyclerView的一个内部类,它主要负责RecyclerView的缓存管理。看下这个内部类:

public final class Recycler {
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;

    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

    private final List<ViewHolder>
            mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

    private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
    int mViewCacheMax = DEFAULT_CACHE_SIZE;

    RecycledViewPool mRecyclerPool;

    private ViewCacheExtension mViewCacheExtension;

    static final int DEFAULT_CACHE_SIZE = 2;

    //代码省略......

    这里对几个成员做一下说明:
    mAttachedScrap:存储当前还在屏幕中的ViewHolder,意思就是刚刚从屏幕中分离出来,然后又即将添加到屏幕上的ViewHolder。
    比如说,RecyclerView数据发生更新,调用notifyDataSetChanged()方法刷新界面,在列表数据刷新的时候存储屏幕内被移除的ViewHolder。
    mChangedScrap:存储的是当前被更新的ViewHolder,比如说调用了adapter调用notifyItemChanged方法。
    mAttachedScrap和mChangedScrap共同组成第一级缓存。也叫屏幕内缓存,不参与RecyclerView在滑动状态下ViewHolder的回收和复用。

    mCachedViews:默认大小为2,也叫离屏缓存。即在Recycler View滑动的时候,回收滑出屏幕的ViewHolder以及提供滑动到屏幕内的ViewHolder的复用。
    mCachedViews构成第二级缓存。

    mViewCacheExtension:是一个抽象静态类,用于充当附加的缓存池。当RecyclerView从第一级缓存中没有找到需要的view时,就会从ViewCacheExtension中查找。
    不过这个缓存是由我们开发者自己维护的,如果没有设置这个缓存,就不会启用它。一般情况下我们也不会去设置它,系统已经预先提供了两级缓存了。
    如果有特殊需求,比如要在调用系统的缓存池之前,返回一个特定的视图,才会用到它。

    RecycledViewPool:非常强大的缓存,其默认大小为5,用于在多个嵌套的RecyclerView之间缓存共享的viewholder,接下来会介绍它。
}

参考网址:https://juejin.im/entry/5c66ce2b51882562c704ebad

从上面我们可以看到,RecyclerView缓存的是viewholder,这也是它跟ListView不同的地方,ListView需要我们手动判断缓存是否为空,而RecyclerView由于缓存的是viewholder,直接就在内部就帮我们判断好了,我们只需要使用即可。

重点看下RecycledViewPool这个缓存类:

/**
 * RecycledViewPool lets you share Views between multiple RecyclerViews.
 * 在多个RecyclerView之间共享缓存的viewholder
 * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
 * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
 * <p>
 * RecyclerView automatically creates a pool for itself if you don't provide one.
 * 如果我们没有为RecyclerView设置RecycledViewPool,RecyclerView会自动为自己创建一个,当然了这个自己自动创建的只能自己单独使用
 */
public static class RecycledViewPool {
    private static final int DEFAULT_MAX_SCRAP = 5;

    /**
     * Tracks both pooled holders, as well as create/bind timing metadata for the given type.
     * <p>
     * Note that this tracks running averages of create/bind time across all RecyclerViews
     * (and, indirectly, Adapters) that use this pool.
     * <p>
     * 对于缓存的ViewHolder的统一管理
     */
    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<>();

    //代码省略......

    /**
     * Sets the maximum number of ViewHolders to hold in the pool before discarding.
     * 设置RecyclerViewPool能缓存的ViewHolder的最大数量。
     */
    public void setMaxRecycledViews(int viewType, int max) {
        ScrapData scrapData = getScrapDataForType(viewType);
        scrapData.mMaxScrap = max;
        final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
        while (scrapHeap.size() > max) {
            scrapHeap.remove(scrapHeap.size() - 1);
        }
    }

    /**
     * Returns the current number of Views held by the RecycledViewPool of the given view type.
     * 获取RecyclerViewPool缓存的ViewHolder数量。
     */
    public int getRecycledViewCount(int viewType) {
        return getScrapDataForType(viewType).mScrapHeap.size();
    }

    /**
     * Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are
     * present.
     * 从RecyclerViewPool中获取一个ViewHolder缓存,并将其从RecyclerViewPool缓存中移除。
     */
    @Nullable
    public ViewHolder getRecycledView(int viewType) {
        //根据viewtype获取缓存
        final ScrapData scrapData = mScrap.get(viewType);
        if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
             //从pool中删除并返回符合条件的viewholder
            return scrapHeap.remove(scrapHeap.size() - 1);
        }
        return null;
    }

    //代码省略......

    /**
     * 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) {
        //获取要缓存的viewholder的viewType
        final int viewType = scrap.getItemViewType();
        //根据viewType来获取viewholder要保存的位置
        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();
        //将viewholder存入缓存中
        scrapHeap.add(scrap);
    }

    //代码省略......

    private ScrapData getScrapDataForType(int viewType) {
        ScrapData scrapData = mScrap.get(viewType);
        if (scrapData == null) {
            scrapData = new ScrapData();
            mScrap.put(viewType, scrapData);
        }
        return scrapData;
    }
}

从名字上看,RecycledViewPool是一个缓存池,其内部实际上是通过一个默认大小为5的ArrayList来实现的,我们也可以手动设置其缓存容量。然后ArrayList又是用ScrapData这个内部类来统一管理,最后再将这个ScrapData放到SparseArray中,组成缓存的最终形势。我们在实现了自己的RecycledViewPool后,只需要调用RecyclerView#setRecycledViewPool(RecycledViewPool)就可以为多个RecyclerView设置共享的RecycledViewPool。
顺带说下,这个SparseArray内部其实是由两个数组来实现的,一个数组存储键,另一个数组存储值,其效率比HashMap要高一点,但是只能存储少量数据,数据量大的话其效率不如HashMap。在这里,键是viewType,值是scrapData。

分析完了Recycler及RecycledViewPool后,我们继续跟进getViewForPosition方法:

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

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

最终调用了 tryGetViewHolderForPositionByDeadline方法。

ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
    //代码省略......
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    // 0) If there is a changed scrap, try to find from there
    if (mState.isPreLayout()) {
        //默认情况下是false,只有动画的情况下才是true
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    // 1) Find by position from scrap/hidden list/cache
    //第一次尝试,从attachScrap和cachedSCrap中查找viewholder
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if (holder != null) {
            if (!validateViewHolderForOffsetPosition(holder)) {
                //如果获取到的viewholder不可用于当前这个位置
                // recycle holder (and unscrap if relevant) since it can't be used
                if (!dryRun) {
                    //如果viewholder可以从scrap / cache 中移除
                    // we would like to recycle this but need to make sure it is not used by
                    // animation logic etc.
                    holder.addFlags(ViewHolder.FLAG_INVALID);
                    //移除这个viewholder
                    if (holder.isScrap()) {
                        removeDetachedView(holder.itemView, false);
                        holder.unScrap();
                    } else if (holder.wasReturnedFromScrap()) {
                        holder.clearReturnedFromScrapFlag();
                    }
                    recycleViewHolderInternal(holder);
                }
                //将viewholder置空,表示没有查找到,继续往下查找
                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
        //第二次尝试,对应hasStableId情况
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if (holder != null) {
                // update position
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true;
            }
        }
        //如果第二次尝试后holder仍为空,并且我们设置了自定义缓存(ViewCacheExtension)
        if (holder == null && mViewCacheExtension != null) {
            //从ViewCacheExtension中查找
            final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
            if (view != null) {
                //根据查找到的view获取相应的viewholder
                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());
                }
            }
        }
        //前面的缓存均为获取到viewholder,从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);
                }
            }
        }
        //从缓存中查找失败,调用adapter的createViewHolder方法创建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 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");
            }
        }
    }

    //代码省略......
    return holder;
}

我们从上往下慢慢看:

根据position获取缓存
if (mState.isPreLayout()) {
    holder = getChangedScrapViewForPosition(position);
    fromScrapOrHiddenOrCache = holder != null;
}

/**
 * Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its
 * {@link LayoutManager} layout items where they will be at the beginning of a set of
 * predictive item animations.
 * 在有动画情况下这里返回true,否则返回false
 */
public boolean isPreLayout() {
    return mInPreLayout;
}

//从mChangedScrap缓存中获取viewholder
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
    //按照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;
}

首先判断isPreLayout()是否为true,这里主要是跟动画有关,如果为true,也就是有动画,则调用 getChangedScrapViewForPosition(position)方法,从这个方法的名字当中我们也可以看出是从changedScraps缓存中获取。
如果上面isPreLayout()为false时或者isPreLayout()为true并且没有获取到viewholder时,开始从从attachScrap和cachedScrap缓存中查找(这里是按照position来查找的)。

if (holder == null) {
    //从attachScrap和cachedScrap缓存中查找viewholder
    holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    if (holder != null) {
        //如果查找到viewholder不适合当前位置
        if (!validateViewHolderForOffsetPosition(holder)) {
            // recycle holder (and unscrap if relevant) since it can't be used
            //dryRun标记表示viewholder是否可以从缓存中移除,为false表示可以移除,这里传入的是false
            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);
                //从缓存中移除viewholder
                if (holder.isScrap()) {
                     removeDetachedView(holder.itemView, false);
                     holder.unScrap();
                } else if (holder.wasReturnedFromScrap()) {
                     holder.clearReturnedFromScrapFlag();
                }
                recycleViewHolderInternal(holder);
            }
            //将viewholder置空,继续下面的查找
            holder = null;
        } else {
            fromScrapOrHiddenOrCache = true;
        }
    }
}

这里重点关注下getScrapOrHiddenOrCachedHolderForPosition这个方法,从名字就可以看出是按照position来查找缓存的。

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())) {
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }
    //dryRun为false
    if (!dryRun) {
        //从hiddenView中查找view
        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.
            //根据查找到的view获取viewholder
            final ViewHolder vh = getChildViewHolderInt(view);
            //将view从hiddenView中移除
            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);
            //将view添加到scrap缓存中(这里是添加到了mAttachedScrap或者mChangedScrap中)
            scrapView(view);
            vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                    | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
            return vh;
        }
    }

    // Search in our first-level recycled view cache.
    //从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
        //如果查找到viewholder是有效的,并且viewholder的位置和position的位置相同
        if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
            if (!dryRun) {
                //从缓存中删除这个viewholder
                mCachedViews.remove(i);
            }
            if (DEBUG) {
                Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                        + ") found match in cache: " + holder);
            }
            //返回holder
            return holder;
        }
    }
    return null;
}

简要总结下getScrapOrHiddenOrCachedHolderForPosition的流程:

  • 1、首先从mAttachedScrap中查找viewholder,如果找到了并且验证符合条件,直接返回。
  • 2、否则从hiddenView中查找view,再根据这个view获取viewholder,并将view从hiddenView中移除,接着将viewholder保存到缓存中(mAttachedScrap或者mChangedScrap),最后返回。
  • 3、如果1和2中都没有获取到,则从mCachedViews中查找viewholder,并校验这个viewholder是否满足条件,如果满足,将其从mCachedViews中删除,并返回viewholder。

沿着tryGetViewHolderForPositionByDeadline方法继续往下分析,上面是按照position来查找缓存的,如果没有查找到,那么按照stableId再次进行查找。

根据itemId和viewType获取缓存
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;
        }
    }

首先通过mAdapter.getItemViewType(offsetPosition)这个方法获取type,这个getItemViewType也是我们设置RecyclerView列表多种布局的一个重要方法,并且后面所有缓存的获取都是根据这个type来的。也就是说前面介绍的也就是注释中 0)1) 获取的缓存是没有区分type,接下来的缓存获取都是区分type的。
接下来判断mAdapter.hasStableIds()是否为true。

/**
 * Returns true if this adapter publishes a unique <code>long</code> value that can
 * act as a key for the item at a given position in the data set. If that item is relocated
 * in the data set, the ID returned for that item should be the same.
 *
 * @return true if this adapter's items have stable IDs
 */
public final boolean hasStableIds() {
    return mHasStableIds;
}

这里关注下getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun)这个方法,这里通过adapter的getItemId方法获取相应的itemId,这个getItemId方法我们可重写也可不重写。

ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
    // Look in our attached views first
    final int count = mAttachedScrap.size();
    //从mAttachedScrap缓存中查找
    for (int i = count - 1; i >= 0; i--) {
        final ViewHolder holder = mAttachedScrap.get(i);
        //判断缓存中的viewholder所在的itemId是否符合传入的itemId
        if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
            //判断type
            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.
                    if (!mState.isPreLayout()) {
                        holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
                                | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
                    }
                }
                //itemId和viewType都满足,直接返回holder
                return holder;
            } else if (!dryRun) {
                //如果viewholder的itemId符合条件,但是viewType不符合条件
                // 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.
                //从缓存中移除viewholder
                mAttachedScrap.remove(i);
                removeDetachedView(holder.itemView, false);
                //将viewholder缓存到RecyclerViewPool中
                quickRecycleScrapView(holder.itemView);
            }
        }
    }

    // Search the first-level cache
    //从mCachedViews缓存中查找
    final int cacheSize = mCachedViews.size();
    for (int i = cacheSize - 1; i >= 0; i--) {
        final ViewHolder holder = mCachedViews.get(i);
        //判断缓存中的viewholder所在的itemId是否符合传入的itemId
        if (holder.getItemId() == id) {
            //判断type
            if (type == holder.getItemViewType()) {
                //如果viewholder的itemId符合条件,但是viewType不符合条件
                if (!dryRun) {
                    //从缓存中移除viewholder
                    mCachedViews.remove(i);
                }
                return holder;
            } else if (!dryRun) {
                //将viewholder从mCachedViews中移除,并缓存到RecyclerViewPool中
                recycleCachedViewAt(i);
                return null;
            }
        }
    }
    return null;
}

这个方法其实和上面的getScrapOrHiddenOrCachedHolderForPosition方法差不多,区别是该方法是按照itemId和viewType来判断的,而getScrapOrHiddenOrCachedHolderForPosition是按照position来判断的。根据itemId和viewType来查找的大致流程如下:

image.png

从自定义缓存中查找(ViewCacheExtension)

当前面的查找都没有找到时,程序就来到了这里,从我们的自定义缓存(ViewCacheExtension)中来查找。

if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
    //从ViewCacheExtension中获取view
    final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
    if (view != null) {
        //根据view获取viewholder
        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());
        }
    }
}

代码很简单,主要是看下这个ViewCacheExtension到底是啥:

/**
 * ViewCacheExtension is a helper class to provide an additional layer of view caching that can
 * be controlled by the developer.
 */
public abstract static class ViewCacheExtension {
    /**
     * Returns a View that can be binded to the given Adapter position.
     */
    public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
}

这里就一个抽象类,内部一个抽象方法。对于这个自定义缓存的介绍,直接引用了【进阶】RecyclerView源码解析(二)——缓存机制里面的评价:

1、首先这个类基本上没有什么限制,也就是说无论是缓存使用的数据结构还有缓存算法(LRU还是什么)完全自定义,都由开发者自己决定,这一点可以说既给了开发者很大的便利,也给开发者带来了很大的隐患。
2、对于平常的缓存,我们的理解在怎么说至少get-add|push-pop都是成对出现,为什么这样说的,也就是缓存至少有进也有出。而这里可以看到这里的抽象类只定义了出的方法,也就是只出不进,进的时机,大小,时效等完全没有规定。

在日常开发中,我们也很少去自定义这一级的缓存,因为它几乎用不上。

从RecyclerViewPool中查找

RecyclerView与ListView的另一个不同之处是RecyclerView提供了RecyclerViewPool,这是一个在多个RecyclerView之间提供共享view的缓存。比较常见的场景是RecyclerView和RecyclerView之间的嵌套,RecyclerViewPool为这些个嵌套的RecyclerView内view的整体复用提供了便利。

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

对于RecyclerViewPool在本文的前面已经有介绍,这里就不再说了,我们继续往下看。

调用adapter的createViewHolder创建ViewHolder

如果经过上面的多级缓存查找仍然没有查找到的话,程序最终会调用调用adapter的createViewHolder方法来创建一个viewholder。

if (holder == null) {
    long start = getNanoTime();
    //代码省略......
    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);
        }
    }
    //代码省略......
}

没什么好说的,直接调用mAdapter.createViewHolder(RecyclerView.this, type)创建,在这个方法里面最终调用了我们继承并实现的onCreateViewHolder方法。

至此,RecyclerView的缓存取出过程总算是分析完了,不过这里也只是简要的介绍了下,还有很多细节由于水平限制没有详细介绍。最后再总结下:

  • RecyclerView一共有三级缓存(也有人说是四级缓存,这个每个人的角度不一样,因此会有一些区别)。第一级缓存由mAttachedScrap,mChangedScrap,mCachedViews共同组成;第二级缓存是ViewCacheExtension;第三级缓存是RecyclerViewPool。
  • RecyclerView在第一级缓存中查找的时候一共查找了两次。第一次是根据position来查找的,调用方法为getScrapOrHiddenOrCachedHolderForPosition;第二次是根据itemId和viewType来查找的,调用方法为getScrapOrCachedViewForId
  • RecyclerView在根据itemId和viewType来查找缓存时,如果发现当viewholder的itemId符合条件而viewType不符合条件时,会将这个viewholder从mAttachedScrap或者mCachedViews中移动到RecyclerViewPool中(也就是将这个viewholder从mAttachedScrap或者mCachedViews中删除同时保存到RecyclerViewPool中)。

二、缓存存入

既然有缓存取出,也应该有缓存的存入。RecyclerView缓存的存入有一个很重要的方法:recycleView(View view)

/**
 * Recycle a detached view. The specified view will be added to a pool of views
 * for later rebinding and reuse.
 *
 * <p>A view must be fully detached (removed from parent) before it may be recycled. If the
 * View is scrapped, it will be removed from scrap list.</p>
 *
 * @param view Removed view for recycling
 * @see LayoutManager#removeAndRecycleView(View, Recycler)
 */
public void recycleView(View view) {
    // This public recycle method tries to make view recycle-able since layout manager
    // intended to recycle this view (e.g. even if it is in scrap or change cache)
    //根据传入的view获取相关的viewholder
    ViewHolder holder = getChildViewHolderInt(view);
    //如果这个holder已经被打上了清除的标记,则将其移除
    if (holder.isTmpDetached()) {
        removeDetachedView(view, false);
    }
    if (holder.isScrap()) {
        //如果这个holder是来自缓存的可见viewholder数组,将其移除
        holder.unScrap();
    } else if (holder.wasReturnedFromScrap()) {
        //如果这个holder是来自缓存的不可见viewholder数组,将其移除
        holder.clearReturnedFromScrapFlag();
    }
    //开始缓存
    recycleViewHolderInternal(holder);
}

该方法首先根据view获取holder,然后判断holder是否已经在缓存中,如果在,将其从缓存中清除,最后调用recycleViewHolderInternal方法开始缓存。

/**
 * 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) {
    //代码省略(这里主要是对holder进行了一些判断)......
    if (forceRecycle || holder.isRecyclable()) {
        //如果当前有缓存,且缓存的数量大于0,并且viewholder的flag是有效的并且不是REMOVED和UPDATE,进行缓存
        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) {
                //移除缓存中的第一个view
                recycleCachedViewAt(0);
                cachedViewSize--;
            }

            //获取要缓存的holder在缓存集合中的position
            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;
        }
        //如果没有缓存的话,则添加到RecyclerViewPool中
        if (!cached) {
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
        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中移除这个holder
    mViewInfoStore.removeViewHolder(holder);
    if (!cached && !recycled && transientStatePreventsRecycling) {
        holder.mOwnerRecyclerView = null;
    }
}

在这里可以看出,RecyclerView的存缓存策略使用的最少使用策略。当我们存储viewholder的时候,会判断这个viewholder是否来自缓存,如果是的话,那么在存缓存的时候就不能覆盖最近比较频繁使用的缓存。
如何判断这个viewholder缓存最近是否频繁使用呢?官方定义了一个辅助类--GapWorker,它有一个内部类LayoutPrefetchRegistryImpl,然后在这个内部类有一个成员数组int[] mPrefetchArray,这个数组是用来记录最近使用过的holder,所以RecyclerView在存缓存的时候会将要保存的viewholder与这个数组里面的viewholder进行匹配,代码如下:

boolean lastPrefetchIncludedPosition(int position) {
    if (mPrefetchArray != null) {
        final int count = mCount * 2;
        for (int i = 0; i < count; i += 2) {
            //如果viewholder是最近使用的viewholder,则返回true
            if (mPrefetchArray[i] == position) return true;
        }
    }
    return false;
}

recycleAndClearCachedViews:将CacheViews中的ViewHolder添加进RecyclerViewHolder,然后清空CacheViews。

void recycleAndClearCachedViews() {
    final int count = mCachedViews.size();
    for (int i = count - 1; i >= 0; i--) {
        //将mCachedViews中的viewholder存储到RecyclerViewPool中
        recycleCachedViewAt(i);
    }
    //清空mCachedViews集合
    mCachedViews.clear();
    if (ALLOW_THREAD_GAP_WORK) {
        mPrefetchRegistry.clearPrefetchPositions();
    }
}

recycleCachedViewAt

void recycleCachedViewAt(int cachedViewIndex) {
    if (DEBUG) {
        Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
    }
    //从mCachedViews中取出viewholder
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    if (DEBUG) {
        Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
    }
    //将viewholder添加到RecyclerViewPool中
    addViewHolderToRecycledViewPool(viewHolder, true);
    //从mCachedViews中移除这个viewholder
    mCachedViews.remove(cachedViewIndex);
}

addViewHolderToRecycledViewPool

void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
    clearNestedRecyclerViewIfNotNested(holder);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
        holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
        ViewCompat.setAccessibilityDelegate(holder.itemView, null);
    }
    if (dispatchRecycled) {
        dispatchViewRecycled(holder);
    }
    holder.mOwnerRecyclerView = null;
    //将viewholder存储到RecyclerViewPool中
    getRecycledViewPool().putRecycledView(holder);
}

参考文章:
【进阶】RecyclerView源码解析(二)——缓存机制
深入浅出 RecyclerView
Android中的缓存艺术,对比RecyclerView与ListView的缓存机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269