Android---RecyclerView的四级缓存

之前写过一片关于RecyclerView的博客,主要是从源码的角度捋了一遍。最近看了看,觉得写的比较粗糙,比较重点的四级缓存没有说的很清楚,所以再写一篇博客来专门说一下RecyclerView的四级缓存

目录

一.Scrap

1.Scrap的缓存过程

2.Scrap的复用

二、CachedViews

三、ViewCacheExtension

四、RecycledViewPool


一.Scrap

Scrap是RecyclerView的第一级缓存,包括mAttachedScrap和mChangedScrap。之前对这两个缓存很不理解,打点调试的时候,发现无论怎么滚动RecyclerView,都没有走到这两个缓存的地方。这是因为这两个缓存和滚动没有关系,只有当界面重新绘制的时候,这两个缓存才会有作用。这两个缓存的区别稍后再说,我们先从源码中分析一下mAttachedScrap和mChangedScrap的缓存和复用过程

1.Scrap的缓存过程

我们先说一下缓存过程的源码:

        private void scrapOrRecycleView(Recycler recycler, int index, View view) {
            final ViewHolder viewHolder = getChildViewHolderInt(view);
            if (viewHolder.shouldIgnore()) {
                if (DEBUG) {
                    Log.d(TAG, "ignoring view " + viewHolder);
                }
                return;
            }
            if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                    && !mRecyclerView.mAdapter.hasStableIds()) {
                removeViewAt(index);
                recycler.recycleViewHolderInternal(viewHolder);
            } else {
                detachViewAt(index);
                recycler.scrapView(view);
                mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
            }
        }

这个方法是LinearLayoutManager中的方法,顺序为:

onLayoutChildren->detachAndScrapAttachedViews->scrapOrRecycleView

onLayoutChildren这个方法我就不多说了,我们直接进入正题

第9~10行:这里的if有三个条件,如果条件不符合,则执行else。而else中的recycler.scrapView方法(第15行)就是向mAttachedScrap或mChangedScrap中存储。我们先看一下这三个条件:

viewHolder.isInvalid():从翻译上来看,是判断ViewHolder是否为无效状态,说实话,我不太明白这个无效状态时什么,但是我们知道它在哪里设置的这个状态就可以了。我们调用setAdapter的时候会有如下的调用链:setAdapter->processDataSetCompletelyChanged->markKnownViewsInvalide,看一下markKnownViewsInvalide方法:

    void markKnownViewsInvalid() {
        final int childCount = mChildHelper.getUnfilteredChildCount();
        for (int i = 0; i < childCount; i++) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && !holder.shouldIgnore()) {
                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
            }
        }
        markItemDecorInsetsDirty();
        mRecycler.markKnownViewsInvalid();
    }

第6行中设置了这个无效的状态。所以第一次数据设置的时候,viewHolder.isInvalid()返回值为true。

viewHolder.isRemoved():这个比较容易理解,viewHolder是否移除,而代码中有一个!,也就是说viewHolder没有移除。调用Adapter的notifyItemRemoved会使这个标记为移除状态,源码就不翻了,有点费劲,而且不那么重要,知道就行

mRecyclerView.mAdapter.hasStableIds():这个值不设置的话,默认为false,而代码中有!,所以条件也成立

综上所述,得出一个结论:

  • 如果是正常的设置数据,比如第一次数据的设置或者滚动的时候,mAttachedScrap和mChangedScrap的缓存不会触发的;
  • 如何调用了适配器的notifyItemRemoved方法,会触发mAttachedScrap和mChangedScrap缓存
  • 如果设置了适配器的stableId为true,会触发mAttachedScrap和mChangedScrap缓存

我们继续看scrapOrRecycleView方法,查看第15行的源代码:

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

向mAttachedScrap中添加,还是向mChangeScrap中添加,主要是if中的3个条件:

holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID):

这个条件表示ViewHolder执行了Remove动作(上面说了)或者为无效状态

!holder.isUpdated():ViewHolder为非更新状态,而调用了adapter的notifyItemChanged方法就会打上更新的标记

canReuseUpdatedViewHolder():一直都为true,忽略,因为基本上前两个条件就有结果了,到不了这里

经过断点调试得出结论:

  • RecyclerView发生了移除的操作,所有的数据依然会存储到mAttachedScrap中,而不会进入到mChangedScrap里(而所借鉴的文章中说,发生移除的时候,移除条目的上方ViewHolder会存入到mAttachedScrap,而下方的ViewHolder会存入到mChangedScrap中,我的结论和他的不太一样)
  • 如果数据发生了改变,调用了适配器的notifyItemChanged方法,那么被修改数据的ViewHolder会存储到mChangedScrap中

2.Scrap的复用

其实mAttachedScrap和mChangedScrap的复用过程还是比较简单的,我就简单贴一张图来说明一下(如果源码不太熟悉的可以看我这篇文章:Android RecyclerView源码完全解析

如果了解RecyclerView源码的话,tryGetViewHolderForPositionByDeadline方法应该会比较熟悉。这个方法里,第一个红框的方法是mChangedScrap的复用过程,而第二个红框的方法中有mAttachedScrap的复用过程,都比较好找,就懒得贴相关方法的源码了。

但是有一个点要特别注意:看过我的博客Android RecyclerView源码完全解析都知道,tryGetViewHolderForPositionByDeadline是在dispatchLayoutStep2中,也就是说,缓存的复用是在dispatchLayout2中。dispatchLayout2调用之后,会调用dispatcheLayoutStep3,而这个方法中有这样一句代码:

mLayout.removeAndRecycleScrapInt(mRecycler);

我们看一下这个方法源码:

        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;
                }
                vh.setIsRecyclable(false);
                if (vh.isTmpDetached()) {
                    mRecyclerView.removeDetachedView(scrap, false);
                }
                if (mRecyclerView.mItemAnimator != null) {
                    mRecyclerView.mItemAnimator.endAnimation(vh);
                }
                vh.setIsRecyclable(true);
                recycler.quickRecycleScrapView(scrap);
            }
            recycler.clearScrap();
            if (scrapCount > 0) {
                mRecyclerView.invalidate();
            }
        }

第20行:这行代码是关键,这里清空了mAttachedScrap和mChangedScrap,我们看一下

        void clearScrap() {
            mAttachedScrap.clear();
            if (mChangedScrap != null) {
                mChangedScrap.clear();
            }
        }

所以说,RecyclerView中的两个Scrap缓存,都是用完即可清空的。说了半天,可能有同学明白了Scrap的过程,但是并不太清楚Scrap缓存的数据对RecyclerView有什么好处?其实RecyclerView的两个Scrap缓存主要是应对比较轻量级的刷新,比如移除一个条目,刷新一个条目等,页面中大部分的ViewHolder都没有变化,除了变化的那一个条目外,其他的没必要重新设置数据,完全可以哪里复用;如果是全部刷新(适配器的notifyDataSetChanged方法),那么不会走Scrap缓存。

二、CachedViews

RecyclerView的CacheView是和滚动相关的缓存,缓存被移出屏幕的View,CacheView的容量为2。CacheView的缓存过程没什么说的,我们看一下复用过程:

依然是在getScrapOrHiddenOrCachedHolderForPosition方法中:

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

 第6行:if的第二个条件,判断了position是否相同,也就是缓存的position和现在要展示的position是否相同。我们假设我们一直朝一个方向滑动,将要展示的position和CachedViews中缓存的View的position是不可能相同的。

那CachedView这个缓存又有什么用呢?其实CachedView的缓存主要是应对来回滑动的情况,这时候CachedView才会真正的起作用,其缓存的ViewHolder不需要重新赋值,就可以直接拿来用了。当然,如果你认为CachedView的默认容量有点小,可以调用setItemViewCacheSize方法来手动设置。

三、ViewCacheExtension

这个缓存需要开发者进行实现,我没用过这个缓存,也没探究过这个缓存,总觉得其他的缓存已经够用了,先简单引用一下其他博客的内容吧,有时间的话,我再补充它的用法:

ViewCacheExtension是缓存拓展的帮助类,额外提供了一层缓存池给开发者。开发者视情况而定是否使用ViewCacheExtension增加一层缓存池,Recycler首先去scrap和CacheView中寻找复用view,如果没有就去ViewCacheExtension中寻找View,如果还是没有找到,那么最后去RecycledViewPool寻找复用的View。
————————————————
原文链接:https://blog.csdn.net/qjyws/article/details/123237071

四、RecycledViewPool

觉得别人说的挺好的,也很好理解

在Scrap、CacheView、ViewCacheExtension都不愿意回收的时候,都会丢到RecycledViewPool中回收,所以RecycledViewPool是Recycler的终极回收站。
RecycledViewPool实际上是以SparseArray嵌套一个ArraryList的形式保存ViewHolder的,因为RecycledViewPool保存的ViewHolder是以itemType来区分的。这样方便不同的itemType保存不同的ViewHolder。它在回收的时候只是回收该viewType的ViewHolder对象,并没有保存原来的数据信息,在复用的时候需要重新走onBindViewHolder()方法重新绑定数据。


可以看出,RecycledViewPool中定义了SparseArray mScrap,它是一个根据不同itemType来保存静态类ScrapData对象的SparseArray,ScrapData中包含了ArrayList mScrapHeap ,mScrapHeap是保存该itemType类型下ViewHolder的ArrayList。
缓存池定义了默认的缓存大小DEFAULT_MAX_SCRAP = 5,这个数量不是说整个缓存池只能缓存这多个ViewHolder,而是不同itemType的ViewHolder的list的缓存数量,即mScrap的数量,说明最多只有5组不同类型的mScrapHeap。mMaxScrap = DEFAULT_MAX_SCRAP说明每种不同类型的ViewHolder默认保存5个,当然mMaxScrap的值是可以设置的。这样RecycledViewPool就把不同ViewType的ViewHolder按类型分类缓存起来。
————————————————
原文链接:https://blog.csdn.net/qjyws/article/details/123237071

鸣谢:Android:深入理解RecyclerView的缓存机制_VoldemortQian的博客-CSDN博客_android recyclerview缓存机制

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值