• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

深入理解Android-RecyclerView的缓存机制

android 搞代码 3年前 (2022-03-01) 40次浏览 已收录 0个评论
文章目录[隐藏]

咱们晓得,RecyclerView在大量数据时仍然能够丝滑般顺畅的滑动,那它到底是怎么实现的呢,而RecyclerView之所以好用得益于它优良的缓存机制。

咱们晓得,RecyclerView自身是一个ViewGroup,因而在滑动时就防止不了增加或移除子View(子View通过RecyclerView#Adapter中的onCreateViewHolder创立),如果每次应用子View都要去从新创立,必定会影响滑动的流畅性,所以RecyclerView通过Recycler来缓存的是ViewHolder(外部蕴含子View),这样在滑动时能够复用子View,某些条件下还能够复用子View绑定的数据。所以实质上来说,RecyclerView之所以可能实现顺畅的滑动成果,是因为缓存机制,因为缓存缩小了反复绘制View和绑定数据的工夫,从而进步了滑动时的性能。

一、缓存

1.1、四级缓存

Recycler缓存ViewHolder对象有4个等级,优先级从高到底顺次为:

  • mAttachedScrap:缓存屏幕中可见范畴的ViewHolder;
  • mCachedViews:缓存滑动时行将与RecyclerView拆散的ViewHolder,默认最大2个;
  • ViewCacheExtension:自定义实现的缓存;
  • RecycledViewPool :ViewHolder缓存池,能够反对不同的ViewType;

1.1.1 mAttachedScrap

mAttachedScrap存储的是以后屏幕中的ViewHolder,mAttachedScrap的对应数据结构是ArrayList,在调用LayoutManager#onLayoutChildren办法时对views进行布局,此时会将RecyclerView上的Views全副暂存到该汇合中,该缓存中的ViewHolder的个性是,如果和RV上的position或者itemId匹配上了那么能够间接拿来应用的,无需调用onBindViewHolder办法。

1.1.2 mChangedScrap

mChangedScrap和mAttachedScrap属于同一级别的缓存,不过mChangedScrap的调用场景是notifyItemChanged和notifyItemRangeChanged,只有发生变化的ViewHolder才会放入到mChangedScrap中。mChangedScrap缓存中的ViewHolder是须要调用onBindViewHolder办法从新绑定数据的。

1.1.3 mCachedViews

mCachedViews缓存滑动时行将与RecyclerView拆散的ViewHolder,按子View的position或id缓存,默认最多寄存2个。mCachedViews对应的数据结构是ArrayList,然而该缓存对汇合的大小是有限度的。

该缓存中ViewHolder的个性和mAttachedScrap中的个性是一样的,只有position或者itemId对应就无需从新绑定数据。开发者能够调用setItemViewCacheSize(size)办法来扭转缓存的大小,该层级缓存触发的一个常见的场景是滑动RecyclerView。当然调用notify()也会触发该缓存。

1.1.4 ViewCacheExtension

ViewCacheExtension是须要开发者本人实现的缓存,基本上页面上的所有数据都能够通过它进行实现。

1.1.5 RecyclerViewPool

ViewHolder缓存池,实质上是一个SparseArray,其中key是ViewType(int类型),value寄存的是 ArrayList< ViewHolder>,默认每个ArrayList中最多寄存5个ViewHolder。

1.2 四级缓存比照

缓存级别 波及对象 阐明 是否从新创立视图View 是否从新绑定数据
一级缓存 mAttachedScrap mChangedScrap 缓存屏幕中可见范畴的ViewHolder false false
二级缓存 mCachedViews 缓存滑动时行将与RecyclerView拆散的ViewHolder,按子View的position或id缓存 false false
三级缓存 mViewCacheExtension 开发者自行实现的缓存
四级缓存 mRecyclerPool ViewHolder缓存池,实质上是一个SparseArray,其中key是ViewType(int类型),value寄存的是 ArrayList< ViewHolder>,默认每个ArrayList中最多寄存5个ViewHolder false true

1.3 调用过程

通常,RecyclerView滑动时会触发onTouchEvent#onMove,回收及复用ViewHolder在这里就会开始。咱们晓得设置RecyclerView时须要设置LayoutManager,LayoutManager负责RecyclerView的布局,蕴含对ItemView的获取与复用。以LinearLayoutManager为例,当RecyclerView从新布局时会顺次执行上面几个办法:

  • onLayoutChildren():对RecyclerView进行布局的入口办法
  • fill(): 负责对残余空间一直地填充,调用的办法是layoutChunk()
  • layoutChunk():负责填充View,该View最终是通过在缓存类Recycler中找到适合的View的

上述的整个调用链:onLayoutChildren()->fill()->layoutChunk()->next()->getViewForPosition(),getViewForPosition()即是是从RecyclerView的回收机制实现类Recycler中获取适合的View。

二、复用流程

RecyclerView对ViewHolder的复用是从LayoutState的next()办法开始的。LayoutManager在布局itemView时,须要获取一个ViewHolder对象,如下所示。

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

next办法调用RecyclerView的getViewForPosition办法来获取一个View,而getViewForPosition办法最终会调用到RecyclerView的tryGetViewHolderForPositionByDeadline办法,而RecyclerView真正复用的外围就在这里。

    @Nullable
    ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
        ViewHolder holder = null;
        // 0) 如果它是扭转的废除的ViewHolder,在scrap的mChangedScrap找
        if (mState.isPreLayout()) {
            holder = getChangedScrapViewForPosition(position);
            fromScrapOrHiddenOrCache = holder != null;
        }
        // 1)依据position别离在scrap的mAttachedScrap、mChildHelper、mCachedViews中查找
        if (holder == null) {
            holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        }

        if (holder == null) {
            final int type = mAdapter.getItemViewType(offsetPosition);
            // 2)依据id在scrap的mAttachedScrap、mCachedViews中查找
            if (mAdapter.hasStableIds()) {
                holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
            }
            if (holder == null && mViewCacheExtension != null) {
                //3)在ViewCacheExtension中查找,个别不必到,所以没有缓存
                final View view = mViewCacheExtension
                        .getViewForPositionAndType(this, position, type);
                if (view != null) {
                    holder = getChildViewHolder(view);
                }
            }
            //4)在RecycledViewPool中查找
            holder = getRecycledViewPool().getRecycledView(type);
            if (holder != null) {
                holder.resetInternal();
                if (FORCE_INVALIDATE_DISPLAY_LIST) {
                    invalidateDisplayListInt(holder);
                }
            }
        }
        //5)到最初如果还没有找到复用的ViewHolder,则新建一个
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
    }

能够看到,tryGetViewHolderForPositionByDeadline()办法别离去scrap、CacheView、ViewCacheExtension、RecycledViewPool中获取ViewHolder,如果没有则创立一个新的ViewHolder。

2.1 getChangedScrapViewForPosition

个别状况下,当咱们调用adapter的notifyItemChanged()办法,数据发生变化时,item缓存在mChangedScrap中,后续拿到的ViewHolder须要从新绑定数据。此时查找ViewHolder就会通过position和id别离在scrap的mChangedScrap中查找。

   ViewHolder getChangedScrapViewForPosition(int position) {
        //通过position
        for (int i = 0; i < changedScrapSize; i++) {
            final ViewHolder holder = mChangedScrap.get(i);
            return holder;
        }
        // 通过id
        if (mAdapter.hasStableIds()) {
            final long id = mAdapter.getItemId(offsetPosition);
            for (int i = 0; i < changedScrapSize; i++) {
                final ViewHolder holder = mChangedScrap.get(i);
                return holder;
            }
        }
        return null;
    }

2.2 getScrapOrHiddenOrCachedHolderForPosition

如果没有找到视图,依据position别离在scrap的mAttachedScrap、mChildHelper、mCachedViews中查找,波及的办法如下。

    ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
        final int scrapCount = mAttachedScrap.size();

        // 首先从mAttachedScrap中查找,精准匹配无效的ViewHolder
        for (int i = 0; i < scrapCount; i++) {
            final ViewHolder holder = mAttachedScrap.get(i);
            return holder;
        }
        //接着在mChildHelper中mHiddenViews查找暗藏的ViewHolder
        if (!dryRun) {
            View view = mChildHelper.findHiddenNonRemovedView(position);
            if (view != null) {
                final ViewHolder vh = getChildViewHolderInt(view);
                scrapView(view);
                return vh;
            }
        }
        //最初从咱们的一级缓存中mCachedViews查找。
        final int cacheSize = mCachedViews.size();
        for (int i = 0; i < cacheSize; i++) {
            final ViewHolder holder = mCachedViews.get(i);
            return holder;
        }
    }

能够看到,getScrapOrHiddenOrCachedHolderForPosition查找ViewHolder的程序如下:

  • 首先,从mAttachedScrap中查找,精准匹配无效的ViewHolder;
  • 接着,在mChildHelper中mHiddenViews查找暗藏的ViewHolder;
  • 最初,从一级缓存中mCachedViews查找。

2.3 getScrapOrCachedViewForId

如果在getScrapOrHiddenOrCachedHolderForPosition没有找到视图,泽通过id在scrap的mAttachedScrap、mCachedViews中查找,代码如下。

  ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
        //在Scrap的mAttachedScrap中查找
        final int count = mAttachedScrap.size();
        for (int i = count - 1; i >= 0; i--) {
            final ViewHolder holder = mAttachedScrap.get(i);
            return holder;
        }

        //在一级缓存mCachedViews中查找
        final int cacheSize = mCachedViews.size();
        for (int i = cacheSize - 1; i >= 0; i--) {
            final ViewHolder holder = mCachedViews.get(i);
            return holder;
        }
    }        

getScrapOrCachedViewForId()办法查找的程序如下:

  • 首先, 从mAttachedScrap中查找,精准匹配无效的ViewHolder;
  • 接着, 从一级缓存中mCachedViews查找;

2.4 mViewCacheExtension

mViewCacheExtension是由开发者定义的一层缓存策略,Recycler并没有将任何view缓存到这里。

if (holder == null && mViewCacheExtension != null) {
        final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
        if (view != null) {
            holder = getChildViewHolder(view);
        }
    }

这里没有自定义缓存策略,那么就找不到对应的view。

2.5 RecycledViewPool

在ViewHolder的四级缓存中,咱们有提到过RecycledViewPool,它是通过itemType把ViewHolder的List缓存到SparseArray中的,在getRecycledViewPool().getRecycledView(type)依据itemType从SparseArray获取ScrapData ,而后再从外面获取ArrayList<ViewHolder>,从而获取到ViewHolder。

    @Nullable
    public ViewHolder getRecycledView(int viewType) {
        final ScrapData scrapData = mScrap.get(viewType);//依据viewType获取对应的ScrapData 
        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;
    }

2.6 创立新的ViewHolder

如果还没有获取到ViewHolder,则通过mAdapter.createViewHolder()创立一个新的ViewHolder返回。

  // 如果还没有找到复用的ViewHolder,则新建一个
  holder = mAdapter.createViewHolder(RecyclerView.this, type);

上面是寻找ViewHolder的一个残缺的流程图:

三、回收流程

RecyclerView回收的入口有很多, 然而不论怎么样操作,RecyclerView 的回收或者复用必然波及到add View 和 remove View 操作, 所以咱们从onLayout的流程动手剖析回收和复用的机制。

首先,在LinearLayoutManager中,咱们来到itemView布局入口的办法onLayoutChildren(),如下所示。

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
            if (state.getItemCount() == 0) {
                removeAndRecycleAllViews(recycler);//移除所有子View
                return;
            }
        }
        ensureLayoutState();
        mLayoutState.mRecycle = false;//禁止回收
        //颠倒绘制布局
        resolveShouldLayoutReverse();
        onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);

        //临时拆散曾经附加的view,行将所有child detach并通过Scrap回收
        detachAndScrapAttachedViews(recycler);
    }

在onLayoutChildren()布局的时候,先依据理论状况是否须要removeAndRecycleAllViews()移除所有的子View,哪些ViewHolder不可用;而后通过detachAndScrapAttachedViews()临时拆散曾经附加的ItemView,并缓存到List中。

detachAndScrapAttachedViews()的作用就是把以后屏幕所有的item与屏幕拆散,将他们从RecyclerView的布局中拿下来,保留到list中,在从新布局时,再将ViewHolder从新一个个放到新的地位下来。

将屏幕上的ViewHolder从RecyclerView的布局中拿下来后,寄存在Scrap中,Scrap包含mAttachedScrap和mChangedScrap,它们是一个list,用来保留从RecyclerView布局中拿下来ViewHolder列表,detachAndScrapAttachedViews()只会在onLayoutChildren()中调用,只有在布局的时候,才会把ViewHolder detach掉,而后再add进来从新布局,然而大家须要留神,Scrap只是保留从RecyclerView布局中以后屏幕显示的item的ViewHolder,不参加回收复用,单纯是为了现从RecyclerView中拿下来再从新布局下来。对于没有保留到的item,会放到mCachedViews或者RecycledViewPool缓存中参加回收复用。

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

下面代码的作用是,遍历所有view,拆散所有曾经增加到RecyclerView的itemView。

   private void scrapOrRecycleView(Recycler recycler, int index, View view) {
        final ViewHolder viewHolder = getChildViewHolderInt(view);
        if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                && !mRecyclerView.mAdapter.hasStableIds()) {
            removeViewAt(index);//移除VIew
            recycler.recycleViewHolderInternal(viewHolder);//缓存到CacheView或者RecycledViewPool中
        } else {
            detachViewAt(index);//拆散View
            recycler.scrapView(view);//scrap缓存
            mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
        }
    }

而后,咱们看detachViewAt()办法拆散视图,再通过scrapView()缓存到scrap中。

    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);//保留到mAttachedScrap中
        } else {
            if (mChangedScrap == null) {
                mChangedScrap = new ArrayList<ViewHolder>();
            }
            holder.setScrapContainer(this, true);
            mChangedScrap.add(holder);//保留到mChangedScrap中
        }
    }

而后,咱们回到scrapOrRecycleView()办法中,进入if()分支。如果viewHolder是有效、未被移除、未被标记的则放到recycleViewHolderInternal()缓存起来,同时removeViewAt()移除了viewHolder。

   void recycleViewHolderInternal(ViewHolder holder) {
           ·····
        if (forceRecycle || holder.isRecyclable()) {
            if (mViewCacheMax > 0
                    && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                    | ViewHolder.FLAG_REMOVED
                    | ViewHolder.FLAG_UPDATE
                    | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {

                int cachedViewSize = mCachedViews.size();
                if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {//如果超出容量限度,把第一个移除
                    recycleCachedViewAt(0);
                    cachedViewSize--;
                }
                     ·····
                mCachedViews.add(targetCacheIndex, holder);//mCachedViews回收
                cached = true;
            }
            if (!cached) {
                addViewHolderToRecycledViewPool(holder, true);//放到RecycledViewPool回收
                recycled = true;
            }
        }
    }

如果符合条件,会优先缓存到mCachedViews中时,如果超出了mCachedViews的最大限度,通过recycleCachedViewAt()将CacheView缓存的第一个数据增加到终极回收池RecycledViewPool后再移除掉,最初才会add()新的ViewHolder增加到mCachedViews中。

剩下不符合条件的则通过addViewHolderToRecycledViewPool()缓存到RecycledViewPool中。

    void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
        clearNestedRecyclerViewIfNotNested(holder);
        View itemView = holder.itemView;
        ······
        holder.mOwnerRecyclerView = null;
        getRecycledViewPool().putRecycledView(holder);//将holder增加到RecycledViewPool中
    }

最初,就是在填充布局调用fill()办法的时候,它会回收移出屏幕的view到mCachedViews或者RecycledViewPool中。

 int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
              recycleByLayoutState(recycler, layoutState);//回收移出屏幕的view
        }
    }

而recycleByLayoutState()办法就是用来回收移出屏幕的view,残缺的流程如下图。


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:深入理解Android-RecyclerView的缓存机制

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址