RecycleView和ListView的缓存回收和复用机制

18 阅读4分钟

1.ReCycleVIew的四级缓存

1727253754481.png

1.1.屏幕内缓存

mAttachedScrap【ArrayList】 用于保存屏幕上可见但是数据未发生变化的ViewHolder createViewHolder x bindViewHolder x

mChangedScrap【ArrayList】 用于保存屏幕上可见的ViewHolder 【当删除Item的时候触发】 createView x bindView √

1.2. 屏幕外缓存 mCacheViews 【ArrayList】 的ViewHolder,默认上限2个 createViewHolder x bindViewHolder x 【如果数据没有有变化时,不需要调用,如果已经变化则需要调用】

1.3.自定义缓存 mViewCacheExtension 不直接使用,一般需要用户自定义

1.4.缓存池 RecycledViewPool 默认上线为5个,技术上可以实现多个 RecycleView复用同一个Pool. createView x bindView √

mAttachedScrap主要用于保存屏幕上可见且数据未发生变化的ViewHolder,而mChangedScrap则用于保存数据已发生变化但仍在屏幕上的ViewHolder,需要重新绑定数据。这两种缓存机制共同作用,使得RecyclerView能够高效地管理和更新视图,特别是在数据集发生变化时

2.RecyclerView复用机制

5685477-2aed28aea28f13f8 (1).png

当RecyclerView要拿一个复用的ViewHolder时:

  • 如果是预加载,则会先去mChangedScrap中精准查找(分别根据position和id)对应的ViewHolder。
  • 如果没有就再去mAttachedScrap和mCachedViews中精确查找(先position后id)是不是原来的ViewHolder。
  • 如果还没有,则最终去mRecyclerPool找,如果itemType类型匹配对应的ViewHolder,那么返回实例,让它重新绑定数据。
  • 如果mRecyclerPool也没有返回ViewHolder才会调用createViewHolder()重新去创建一个

这里有几点需要注意:

  • 在mChangedScrap、mAttachedScrap、mCachedViews中拿到的ViewHolder都是精准匹配。
  • mAttachedScrap和mCachedViews没有发生变化,是直接使用的。
  • mChangedScrap由于发生了变化,mRecyclerPool由于数据已被抹去,所以都需要调用 onBindViewHolder()重新绑定数据才能使用。
public final class Recycler {
       /**
         * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
         * cache, the RecycledViewPool, or creating it directly.
         * 
         * 尝试通过从Recycler scrap缓存、RecycledViewPool查找或直接创建的形式来获取指定位置的 
      ViewHolder。
         * ...
         */
       public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
        }

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

        @Nullable
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            if (mState.isPreLayout()) {
                // 0 尝试从mChangedScrap中获取根据postion,id获取ViewHolder对象
                holder = getChangedScrapViewForPosition(position);
                ...
            }
            if (holder == null) {
                // 1.1 尝试根据position从mAttachedScrap或mCachedViews中获取ViewHolder对象
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                ...
            }
            if (holder == null) {
                ...
                final int type = mAdapter.getItemViewType(offsetPosition);
                if (mAdapter.hasStableIds()) {
                    // 1.2 尝试根据id从mAttachedScrap或mCachedViews中获取ViewHolder对象
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                    ...
                }
                if (holder == null && mViewCacheExtension != null) {
                    // 2 尝试从mViewCacheExtension中获取ViewHolder对象
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        ...
                    }
                }
                if (holder == null) { // fallback to pool
                    // 3 尝试从mRecycledViewPool中获取ViewHolder对象
                    holder = getRecycledViewPool().getRecycledView(type);
                    ...
                }
                if (holder == null) {
                    // 4.1 回调createViewHolder方法创建ViewHolder对象及其关联的视图
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    ...
                }
            }

            if (mState.isPreLayout() && holder.isBound()) {
                ...
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                ...
                // 4.1 回调bindViewHolder方法提取数据填充ViewHolder的视图内容
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }
            ...
            return holder;
        }
        ...
    }

3.RecycleView【回收机制】

(1).如果RecyclerView不滚动情况下缓存(比如删除item)、重新布局时。是

  • 把屏幕上的ViewHolder与屏幕分离下来,存放到Scrap中,不发生改变的ViewHolder存放到mAttachedScrap中,如果删除一个item时,在onLayoutChild方法中,会先把当前屏幕中的ViewHolder 先缓存到mChangedScrap中,填充View的时候,会从mChangedScrap中获取。
  • 剩下ViewHolder会按照mCachedViews > RecycledViewPool的优先级缓存到mCachedViews或者RecycledViewPool中。

(2).如果是RecyclerView滚动情况下缓存(比如滑动列表),在滑动时填充布局。

  • 先移除滑出屏幕的item,mCachedViews优先缓存这些ViewHolder。
  • 由于mCachedViews最大容量为2,当mCachedViews满了以后,会利用先进先出原则,把旧的ViewHolder存放到RecycledViewPool中后移除掉,腾出空间,再将新的ViewHolder添加到mCachedViews中。
  • 最后剩下的ViewHolder都会缓存到终极回收池RecycledViewPool中,它是根据itemType来缓存不同类型的ArrayList,最大容量为5。

4.ListView二级缓存(AbsListView.RecycleBin)

4.1.mActivieViews 【View[]】 缓存屏幕内的itemView createView x bindView x 4.2.mScrapViews 【ArrayList】 缓存离开屏幕的itemView createView x bindView √

4.3.缓存读取机制如下图:

image.png image.png

5.Recycleview缓存机制的优势 5.1.mCahceView的使用,可以做到屏幕外的itemView进入屏幕时,不需要bindView就可以快速复用。 5.2.在特定的场景下,比如ViewPager+多个列表页,可以对RecycleViewPool进行复用

6.ListView和RecycleView的区别