RecyclerView的复用与回收机制

前言

最近学习了RecyclerView的进阶使用,同时也学习了它的缓存机制,以下是通过看它相应的源码去了解它的获取View(ViewHolder)缓存的过程。

寻找获取缓存起点

获取缓存的起点为绘制流程的onLayout方法,然后追寻相关方法:dispatchLayout()—>dispatchLayoutStep1()—>mLayout.onLayoutChildren(mRecycler, mState)—>fill(recycler, mLayoutState, state, false)—>layoutChunk(recycler, state, layoutState, layoutChunkResult)—>layoutState.next(recycler)—>recycler.getViewForPosition(mCurrentPosition)

Recycler的getViewForPosition方法便是获取View缓存的入口,因为主要是看获取缓存的过程,所以自己也就没有去关注RecyclerView分发layout的逻辑。

获取缓存机制

获取到的其实是ViewHolder,然后从ViewHolder获取对应的View。

Recycler成员

//成员
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();//未分离
ArrayList<ViewHolder> mChangedScrap = null;//已改变

final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();//缓存列表

...

private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;//缓存限度

RecycledViewPool mRecyclerPool;//缓存池

private ViewCacheExtension mViewCacheExtension;//用户自定义缓存策略

获取方法:

 ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
                
           ......
           
            ViewHolder holder = null;
            // 1. 动画执行时调用
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 2. 通过position获取mAttachedScrap缓存中的ViewHolder,或者通过获取被隐藏但没被移除的View去获取ViewHolder
            if (holder == null) {
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
            }
            if (holder == null) {
               ......
                // 3.根据id和viewType去获取ViewHolder
                if (mAdapter.hasStableIds()) {
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                   ......
                }
                //4.根据自定义获取缓存策略获取ViewHolder
                if (holder == null && mViewCacheExtension != null) {
                  
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                      ......
                    }
                }
                if (holder == null) { // fallback to pool
                    ......
                    //5. 根据viewType从缓存池中获取
                    holder = getRecycledViewPool().getRecycledView(type);
                   ......//重置ViewHolder操作
                }
                if (holder == null) {
                    ......
                    //6. 调用实现的方法去创建一个ViewHolder
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                  ......
                }
            }
			......
            return holder;
        }
  1. getChangedScrapViewForPosition(position):有动画的时候,才会执行此方法,通过position从mChangedScrap获取ViewHolder。
  2. getScrapOrHiddenOrCachedHolderForPosition(position, dryRun)中的部分代码:
//1. 试着从mAttachedScrap获取
     	for (int i = 0; i < scrapCount; i++) {
                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;
                }
            }
	//2. 如果找不到合适的,则获取隐藏但未被移除的view
	View view = mChildHelper.findHiddenNonRemovedView(position);
	//通过View的LayoutParam获取VH
	final ViewHolder vh = getChildViewHolderInt(view);
	//3. 从mCacheViews获取
	for (int i = 0; i < cacheSize; i++) {
          final ViewHolder holder = mCachedViews.get(i);
          if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
              ......
              return holder;
           }
      }
  1. getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun)中的部分代码:
	//1. 根据id和viewType匹配去获取holder从mAttachedScrap获取VH
		for (int i = count - 1; i >= 0; i--) {
                final ViewHolder holder = mAttachedScrap.get(i);
                if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
                    if (type == holder.getItemViewType()) {
                        holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                        ......
                        return holder;
                    } 
                    ......
                }
            }
		//2. 根据id和viewType获取holder从mCachedViews获取VH
         for (int i = cacheSize - 1; i >= 0; i--) {
                    final ViewHolder holder = mCachedViews.get(i);
                    if (holder.getItemId() == id) {
                        if (type == holder.getItemViewType()) {
                            if (!dryRun) {
                                mCachedViews.remove(i);
                            }
                            return holder;
                        } else if (!dryRun) {
                            recycleCachedViewAt(i);
                            return null;
                        }
                    }
                }
  1. mViewCacheExtension:自定义获取缓存策略,需要实现public abstract View getViewForPositionAndType(Recycler recycler, int position, int type)方法,这给我们提供了很大的灵活。通过这个方法获取View,最后再由View的LayoutParams获取ViewHolder。
  2. getRecycledViewPool().getRecycledView(type):通过type去获取对应的ViewHolder。

RecycledViewPool的成员如下:

//存储某个type的ViewHolder集合
		static class ScrapData {
		            ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
		            int mMaxScrap = DEFAULT_MAX_SCRAP;
		            long mCreateRunningAverageNs = 0;
		            long mBindRunningAverageNs = 0;
		        }
		 //存储各个type对应的ViewHolder集合
        SparseArray<ScrapData> mScrap = new SparseArray<>();

getRecycledView(type)方法:

//根据viewType获取对应的ScrapData,再从scrapHeap中获取VH并移除
 public ViewHolder getRecycledView(int viewType) {
      final ScrapData scrapData = mScrap.get(viewType);
      if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
         final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
          return scrapHeap.remove(scrapHeap.size() - 1);
      }
      return null;
 }
  1. 到最后还是没能从缓存中获取ViewHolder,则调用需要我们实现Adapter中的createViewHolder(RecyclerView.this, type)方法。

到此从缓存中获取ViewHolder的大致流程也就走完了,下边是如何将滑出屏幕的ViewHolder缓存起来。

回收机制

入口为Recycler的recycleViewHolderInternal方法:

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)) {
            //如果mCachedViews满了(默认为2),则拿出一个放到RecycledViewPool中
            int cachedViewSize = mCachedViews.size();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                recycleCachedViewAt(0);
                cachedViewSize--;
            }

            int targetCacheIndex = cachedViewSize;
            if (ALLOW_THREAD_GAP_WORK
                    && cachedViewSize > 0
                    && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                // when adding the view, skip past most recently prefetched views
                int cacheIndex = cachedViewSize - 1;
                while (cacheIndex >= 0) {
                    int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                    if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                        break;
                    }
                    cacheIndex--;
                }
                targetCacheIndex = cacheIndex + 1;
            }
            //将新的ViewHolder放进mCachedViews中
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        if (!cached) {
            //缓存失败,直接放进RecycledViewPool,如果缓存池满了(默认每个类型为5),则不会不缓存
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
       ......
    }
   ......
}

总结

  1. 缓存ViewHolder时,先查看mCachedViews,如果mCachedViews(默认最大为2)满了的时候,则先从中拿出一个放到RecycledViewPool(默认每个类型最大为5)中,然后再将新的放到mCachedViews中。
  2. 如果缓存到mCachedViews失败,则直接存放到RecycledViewPool中。
  3. 获取缓存时,是先复用再回收的,所以当列表第一次滑动时,mCachedViews和RecycledViewPool都还没缓存,所以需要创建新的ViewHolder。
  4. 当mCachedViews有缓存时,会从mCachedViews中获取,如果是当前itemView的ViewHolder,则无需创建和绑定数据。
  5. 当没能从mCachedViews获取到匹配的ViewHolder,则从RecycledViewPool,由于获取到的ViewHolder调用了resetInternal方法重置了成员,所以它最后会调用onBindViewHolder方法去重新绑定数据。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值