RecyclerView学习(三)之缓存原理分析

RecyclerView学习(三)之缓存原理分析

  • 缓存现象
  • 获取Holder
    • Recycler
    • RecycledViewPool
    • ViewCacheExtension
    • ViewHolder状态
  • 缓存获取
    • 第一步获取获取缓存
    • 第二步获取获取缓存
    • 第三步获取获取缓存

学而不思则罔,思而不学则殆
相关文档:
RecyclerView学习(一)之应用
RecyclerView学习(二)之缓存探索
RecyclerView学习(三)之缓存原理分析

结合上一篇文章:RecyclerVIew缓存探索
本篇文章来分析研究一下缓存原理

缓存现象

从上一篇文章分析我们得出,当我们列表滑动到一定的时候就不在调用onCreateViewHolder方法,产生新的Holder;但是我们的onBindViewHolder方法却还在回调,返回的Holder却是老的对象;而且Holder的总个数大于屏幕上实际显示的列表个数

获取Holder

看一下代码,这段代码在RecyclerView.Recycler中,是RecyclerView中的一个final类型的内部类,这个方法代码比较长,我们删除干扰项,理出主干逻辑

/**
         * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
         * cache, the RecycledViewPool, or creating it directly.
         * 

* If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return * rather than constructing or binding a ViewHolder if it doesn't think it has time. * If a ViewHolder must be constructed and not enough time remains, null is returned. If a * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this. * * @param position Position of ViewHolder to be returned. * @param dryRun True if the ViewHolder should not be removed from scrap/cache/ * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should * complete. If FOREVER_NS is passed, this method will not fail to * create/bind the holder if needed. * * @return ViewHolder for requested position */ @Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { ... 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) { //根据Adapter的getItemViewType获取不同的type 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 && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); if (view != null) { holder = getChildViewHolder(view); ... } } ... if (holder == null) { // fallback to pool holder = getRecycledViewPool().getRecycledView(type); ... } if (holder == null) { long start = getNanoTime(); ... //调用mAdapter的createViewHolder创建新的Holder-说明缓存中没有可复用的,需要新建 holder = mAdapter.createViewHolder(RecyclerView.this, type); ... long end = getNanoTime(); mRecyclerPool.factorInCreateTime(type, end - start); if (DEBUG) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); } } } ... return holder; }

我们先来看一下这个方法的说明

Attempts to get the ViewHolder for the given position, either from the Recycler scrap, cache, the RecycledViewPool, or creating it directly.
尝试从回收器废件、缓存、回收的viewpool或直接创建它 的方式来获取ViewHolder通过给定的position

从这段代码中我们发现holder总共有5次被赋值的逻辑

  1. holder = getChangedScrapViewForPosition(position);
  2. holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
  3. holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
  4. holder = getChildViewHolder(view);
  5. holder = mAdapter.createViewHolder(RecyclerView.this, type);

以上这5次赋值逻辑除了最后一次,前面的4次都是去缓存中拿数据,这里只是拿缓存,那什么时候设置缓存呢?后面会分析
这里拿缓存也是有优先级的

Recycler

缓存的主要控制类,如下是内部属性,

    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;
        ...方法...
    }

结论

  1. mAttachedScrap 是一个对象为ViewHolder的列表
  2. mChangedScrap 也是一个对象为VIewHolder的列表
  3. mCachedViews 也是一个对象为VIewHolder的列表
  4. mUnmodifiableAttachedScrap 也是一个对象为VIewHolder的列表
  5. mRecyclerPool 看名称是缓存池
  6. mViewCacheExtension 看名称也是缓存
  7. DEFAULT_CACHE_SIZE 默认大小2
  8. Recycler是个存在ViewHolder和缓存的类

在来看看RecycledViewPool 和ViewCacheExtension 是什么样的?

RecycledViewPool

先来看看RecycledViewPool 主要源码,方法什么的先不看,只看属性

    /**
     * RecycledViewPool lets you share Views between multiple RecyclerViews.
     * 

* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. *

* RecyclerView automatically creates a pool for itself if you don't provide one. */ 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<>(); private int mAttachCount = 0; //...方法... }

RecycledViewPool 类说明,很重要,也说的很清楚

    /**
     * RecycledViewPool lets you share Views between multiple RecyclerViews.
     * 

* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}. *

* RecyclerView automatically creates a pool for itself if you don't provide one. */

  1. RecycledViewPool 让你可以在不同的RecyclerView之前分享View
  2. 如果你想在不同的RecyclerView之前回收View,需要创建一个RecycledViewPool,通过setRecycledViewPool设置
  3. RecyclerView会自动为自己创建池,如果你没有提供一个
  4. 说的很明白,是什么,怎么用,以及RecyclerView的兜底方案

再来看看RecycledViewPool 的对象属性

  1. DEFAULT_MAX_SCRAP 定义了一个默认的最大缓存个数
  2. mScrap 是一个对象为ScrapData的列表
  3. ScrapData本身也是一个保留ViewHolder的列表,默认对大个数为DEFAULT_MAX_SCRAP
  4. mAttachCount 计数的
  5. mScrap 列表的key值是 ViewHolder Type
  6. 可用通过setMaxRecycledViews(int viewType, int max)设置不同的tyep的最大缓存数量
  7. 如果我们什么都不是设置的话,就会每个tyep默认有5个缓存在这儿(讲解了上一篇文章的一个问题,缓存的个数问题)

ViewCacheExtension

ViewCacheExtension是一个抽象类,需要我们去实现,先看看源码

    /**
     * ViewCacheExtension is a helper class to provide an additional layer of view caching that can
     * be controlled by the developer.
     * 

* 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}. *

* 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. *

* This method should not 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. *

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

代码很少,一个抽象类+一个抽象方法

ViewHolder状态

这个状态不得不说,缓存使用都是根据ViewHolder的状态来进行判断的

状态 说明
FLAG_BOUND = 1 << 0 ViewHolder已经绑定好
FLAG_UPDATE = 1 << 1 数据已经过期,需要重新绑定
FLAG_INVALID = 1 << 2 ViewHolder的数据是无效的,类型有可能不在匹配,需要完全重新绑定
FLAG_REMOVED = 1 << 3 表示数据已经从集合中移除,但是视图任然可以用作动画
FLAG_NOT_RECYCLABLE = 1 << 4 这个ViewHolder不能回收复用;通过setIsRecyclable方法设置;一般用于在动画期间保持视图
FLAG_RETURNED_FROM_SCRAP = 1 << 5 视图是从缓存中返回的;当从废件返回时,ViewHolder将保留在废件列表中,直到布局过程的结束,如果未将其添加回RecyclerView,则由RecyclerView回收
FLAG_IGNORE = 1 << 7 此ViewHolder完全由LayoutManager管理。除非更换LayoutManager,否则我们不会对其进行报废、回收或移除。它对LayoutManager仍然是完全可见的。
FLAG_TMP_DETACHED = 1 << 8 当视图与父视图分离时,我们设置此标志,以便在需要删除或重新添加视图时采取正确的操作。
FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9 当我们无法再确定此视图支架的适配器位置时设置,直到它反弹到新位置为止
FLAG_ADAPTER_FULLUPDATE = 1 << 10 调用addChangePayload(null)时设置
FLAG_MOVED = 1 << 11 动画相关属性
FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12 当视图固定器出现在预布局中时,由ItemAnimator使用,我的理解为预布局
以上的flag我也没有了解全,只是知道有这么一个东西,用来标识ViewHolder的各种状态,标识各种状态的flag的其中一个作用就是复用缓存判断,想了解各种状态什么时候赋值的话,需要仔细去阅读源码,调试其赋值的逻辑,作者后面如果有时间会针对这个flag做一个状态流转图(立一个flag)

但是以上都不影响我们研究缓存原理,我们只要知道复用缓存的时候需要判断这个状态,至于状态怎么来的可以暂时不关心,这个属于另一个研究课题
现在我们来看一下获取ViewHolder的五大步骤,就是开头的五个复制方法

缓存获取

第一步获取获取缓存

// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
    holder = getChangedScrapViewForPosition(position);
    fromScrapOrHiddenOrCache = holder != null;
}

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

分析

  1. 这里有一个 mState{final State mState = new State();},你只要了解它是一个RecyclerView的状态类就可以了
  2. 如果成立会从mChangedScrap这个列表中获取有没有缓存,如果有,会设置Flag = ViewHolder.FLAG_RETURNED_FROM_SCRAP,在返回

第二步获取获取缓存

            // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
                //1.1 从缓存中获取ViewHolder 
                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;
                    }
                }
            }
        /**
         * Returns a view for the position either from attach scrap, hidden children, or cache.
         *
         * @param position Item position
         * @param dryRun  Does a dry run, finds the ViewHolder but does not remove
         * @return a ViewHolder that can be re-used for this position.
         */
        ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
            final int scrapCount = mAttachedScrap.size();

            // Try first for an exact, non-invalid match from scrap.
            for (int i = 0; i < scrapCount; i++) {
                //从mAttachedScrap获取
                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;
                }
            }

            if (!dryRun) {
                //从隐藏的视图中获取
                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);
                    vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                            | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    return vh;
                }
            }

            // Search in our first-level recycled view cache.
            final int cacheSize = mCachedViews.size();
            for (int i = 0; i < cacheSize; i++) {
                //从mCachedViews中获取
                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;
        }

以上获取的缓存都是系统提供的

第三步获取获取缓存

            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.
                    //从ViewCacheExtension中获取缓存
                    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");
                    }
                    //从RecycledViewPool中获取缓存
                    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;
                    }
                    //新建ViewHolder
                    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");
                    }
                }
            }

以上就是缓存原理分析,有很多地方不是清楚,欢迎指教,共同进步

你可能感兴趣的:(android)