RecyclerView源码分析(二)--缓存机制(一)

385 阅读2分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战

我们理解RecyclerView的缓存的话,可以从三个问题入手:

  • 缓存是什么?复用是什么
  • 缓存到哪里去了?又从哪里获得的复用
  • 什么时候缓存?什么时候复用

一、 ViewHolder

1.为什么要使用ViewHolder

ItemView 和 ViewHolder 的关系是一对一,即一个 ViewHolder 对应一个 ItemView。RecyclerView的缓存机制,其实就是ViewHolder

RecyclerView的缓存分为四级:

  • mChangeScrap 和 mAttachedScrap (屏幕内的缓存数据,mChangeScrap表示数据已经改变的ViewHolder列表,mAttachedScrap表示数据已经改变的ViewHolder列表)
  • mCachedViews (刚刚移除屏幕的缓存数据)
  • mViewCacheExtension (开发者自定义缓存,需要自己管理View的创建和缓存)
  • RecycledViewPool (ViewHolder的缓存池)

二、Recycler

Recycler用于管理已经废弃或与RecyclerView分离的ViewHolder。ViewHolder的缓存是由屏幕内的缓存数据mChangeScrap、 mAttachedScrap 和 刚刚移除屏幕的缓存mCachedViews组成的。RecycledViewPool是用来缓存整体的ViewHolder,当mCachedViews缓存满了,会把先缓存进去的ViewHolder移除并缓存到RecycledViewPool

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

当ViewHolder被创建后,就要被缓存,然后复用,那是什么时候添加到缓存中的呢?

  • 当ViewHolder无效,并且被remove,执行recycler.recycleViewHolderInternal(viewHolder)方法,将ViewHolder添加到CacheView和Pool中
  • 当deatch时,将View加入到scrapView()中
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
     final ViewHolder viewHolder = getChildViewHolderInt(view);
     . . .
     //ViewHolder无效,并且已经被移除
      if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                    && !mRecyclerView.mAdapter.hasStableIds()) {
           removeViewAt(index);
           recycler.recycleViewHolderInternal(viewHolder);
       } else {
           detachViewAt(index);
           recycler.scrapView(view);
           mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
       }
}

我们再看看scrapView()的具体逻辑

  • 获取对应的ViewHolder
  • 判断该ViewHolder是否分离,如果为分离添加到mAttachedScrap
  • 如果已经分离添加到mChangedScrap
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()) {
         ...
     }
         holder.setScrapContainer(this, false);
         mAttachedScrap.add(holder);
     } else {
         if (mChangedScrap == null) {
             mChangedScrap = new ArrayList<ViewHolder>();
         }
         holder.setScrapContainer(this, true);
         mChangedScrap.add(holder);
    }
}

如果是已经在屏幕外的缓存呢?

屏幕外的缓存,我们认为是二级缓存,使用的是mCachedViews,缓存复用时需要匹配position,这时集合里面的ViewHolder信息还在,不需要重新onBindViewHolder,直接添加到Recycler显示,它的默认容量是2

如果存满的话怎么办呢?

如果存满的话,就会移除最后一个缓存,加到RecycledViewPool中

void recycleCachedViewAt(int cachedViewIndex) {
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    ...
    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}

我们再来看看RecycledViewPool

RecycledViewPool的默认容量的5

private static final int DEFAULT_MAX_SCRAP = 5;

RecycledViewPool保存逻辑为:

  • 获取ViewHolder布局类型
  • 根据布局类型获取ViewHolder
  • 判断缓存池大小,默认5个
  • 同一种viewtype只保存5个ViewHolder
  • 清空ViewHolder记录
public void putRecycledView(ViewHolder scrap) {
   final int viewType = scrap.getItemViewType();
   final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
   if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
       return;
    }
     scrap.resetInternal();
      scrapHeap.add(scrap);
}

这篇主要讲的如何添加到缓存,下一篇主要讲如果从缓存取出来