RecyclerView的缓存机制——复用机制

前面我们在讲RecyclerView回收机制的时候已经提及了它复用机制。但是并没有沿着复用分支继续向下研究,这里将补齐复用分支的源码分析,让我们来看一下RecyclerView是如何实现VH复用的。

在讲回收机制的时候我们说回收机制有两个入口onTouch和onLayout。相应的复用同样也是两个入口。但是两个入口的复用逻辑都是从LinearLayoutManager.layoutChunk()开始。所以这里我们就只需要挑选onTouch一条线分析就可以了。

这里还是要提一下RecyclerView的四级缓存

 

缓存结构

说明

一级缓存

mChangedScrap 与 mAttachedScrap

onLayout过程中屏幕内或正在移出屏幕的Item

二级缓存

mCachedViews

扫描二维码关注公众号,回复: 9458963 查看本文章

刚刚移除屏幕的Item,默认大小2

三级缓存

mViewCacheExtension

Google留给开发者自定义的缓存

四级缓存

mRecyclerPool

当mCachedViews缓存满后会根据FIFO的规则将二级缓存中移出的ViewHolder缓存到RecycledViewPool中,默认大小5

 复用逻辑入口:Recycler. tryGetViewHolderForPositionByDeadline

ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
          .....
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
		1、第一次尝试复用:如果有改变的表项则从mChangedScrap中查找VH
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
		2、第二次尝试复用:通过position位置在mAttachedScrap和mCachedViews中查找VH
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                ......
            }
            if (holder == null) {
               ....
                // 2) Find from scrap/cache via stable ids, if exists
                if (mAdapter.hasStableIds()) {//如果存在StableIds
		3、第三次尝试复用:通过StableIds在mAttachedScrap和mCachedViews中查找
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                   ....
                }
                if (holder == null && mViewCacheExtension != null) {
                    // We are NOT sending the offsetPosition because LayoutManager does not
                    // know it.
		4、第四次尝试复用:如果存在自定义缓存,则通过自定义缓存查询View,再通过View获取与其绑定的VH
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                     .....
                    }
                }
                if (holder == null) { // fallback to pool
                    ....
		5、第五次尝试复用:通过回收池RecyclerViewPool查找VH
                    holder = getRecycledViewPool().getRecycledView(type);
                   ....
                }
                if (holder == null) {
                  .....
		6、创建新VH:如果四级缓存中都没有查找到合适的VH,则创建新的VH
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
				  ....
                }
            }
			......
            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
                // do not update unless we absolutely have to.
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
                ......
                7、绑定VH:VH未绑定或者需要更新或者无效状态时,重新绑定VH
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }

            final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            final LayoutParams rvLayoutParams;
            if (lp == null) {
                rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else if (!checkLayoutParams(lp)) {
                rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                holder.itemView.setLayoutParams(rvLayoutParams);
            } else {
                rvLayoutParams = (LayoutParams) lp;
            }
            rvLayoutParams.mViewHolder = holder;//将VH和View建立绑定关系
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            return holder;
        }

1、第一次尝试复用

通过StableIds在mAttachedScrap和mCachedViews中查找VH

ViewHolder getChangedScrapViewForPosition(int position) {
            // If pre-layout, check the changed scrap for an exact match.
            final int changedScrapSize;
            if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
                return null;
            }
            // find by position
            for (int i = 0; i < changedScrapSize; i++) {
                final ViewHolder holder = mChangedScrap.get(i);
				查找mChangedScrap并匹配position
                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    return holder;
                }
            }
            // find by id
            if (mAdapter.hasStableIds()) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
                    final long id = mAdapter.getItemId(offsetPosition);
                    for (int i = 0; i < changedScrapSize; i++) {
                        final ViewHolder holder = mChangedScrap.get(i);
						//查找mChangedScrap,并匹配ItemId
                        if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
                            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                            return holder;
                        }
                    }
                }
            }
            return null;
        }

2、第二次尝试复用

通过position位置在mAttachedScrap和mCachedViews中查找VH

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

            // Try first for an exact, non-invalid match from scrap.
            for (int i = 0; i < scrapCount; i++) {
                final ViewHolder holder = mAttachedScrap.get(i);
				//查找mAttachedScrap并匹配position,且VH有效未移除
                if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
                        && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                    return holder;
                }
            }
			.....
 
            // Search in our first-level recycled view cache.
            final int cacheSize = mCachedViews.size();
            for (int i = 0; i < cacheSize; i++) {
                final ViewHolder holder = mCachedViews.get(i);
				//查找mCachedViews并匹配position,且VH有效
                // 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) {
                    if (!dryRun) {
                        mCachedViews.remove(i);
                    }
                    ....
                    return holder;
                }
            }
            return null;
        }

3、第三次尝试复用

通过StableIds在mAttachedScrap和mCachedViews中查找VH

 ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
            // Look in our attached views first
            final int count = mAttachedScrap.size();
            for (int i = count - 1; i >= 0; i--) {
                final ViewHolder holder = mAttachedScrap.get(i);
				//查找mAttachedScrap,并匹配ItemId
                if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
                    if (type == holder.getItemViewType()) {//匹配ViewType
                       .....
                        return holder;
                    } else if (!dryRun) {
					//如果View正在执行动画等,则从mAttachedScrap中移除
                        // if we are running animations, it is actually better to keep it in scrap
                        // but this would force layout manager to lay it out which would be bad.
                        // Recycle this scrap. Type mismatch.
                        mAttachedScrap.remove(i);
                        removeDetachedView(holder.itemView, false);
                        quickRecycleScrapView(holder.itemView);
                    }
                }
            }

            // Search the first-level cache
            final int cacheSize = mCachedViews.size();
            for (int i = cacheSize - 1; i >= 0; i--) {
                final ViewHolder holder = mCachedViews.get(i);
				//查找mCachedViews,并匹配ItemId
                if (holder.getItemId() == id) {
                    if (type == holder.getItemViewType()) {
                        if (!dryRun) {
                            mCachedViews.remove(i);
                        }
                        return holder;
                    } else if (!dryRun) {
                        recycleCachedViewAt(i);
                        return null;
                    }
                }
            }
            return null;
        }

 4、第四次尝试复用

如果存在自定义缓存,则通过自定义缓存查询View,再通过View获取与其绑定的VH

ViewCacheExtension 是一个抽象接口,需要开发者自定义并通过RecyclerView设置。

public abstract static class ViewCacheExtension {

        /**
         * Returns a View that can be binded to the given Adapter position.
         * 通过自定义的缓存返回对应position和type的View
         */
        @Nullable
        public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
                int type);
    }

5、第五次尝试复用

通过回收池RecyclerViewPool查找VH

 RecyclerViewPool的数据结构有点像Map,Key/Value类型的数据结构,不过Key值的类型是固定的int型,对应着ViewType。如下:

public ViewHolder getRecycledView(int viewType) {
			//通过ViewType匹配到对应的Value值ScrapData。
            final ScrapData scrapData = mScrap.get(viewType);
            if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
                final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
				//取出最新存入的VH,并从缓存的List中移除
                return scrapHeap.remove(scrapHeap.size() - 1);
            }
            return null;
        }

6、创建新VH

如果四级缓存中都没有查找到合适的VH,则创建新的VH

有人可能会说这里有什么好讲的,不就是调用Adapter的createViewHolder方法创建新的VH吗。确实,这一块是没什么好讲的 ,但是我想讲的并不是createViewHolder这个方法,而是createViewHolder之后调用另外一个方法mRecyclerPool.factorInCreateTime(type, end - start)。这个方法是干嘛的呢?我们来看看:

void factorInCreateTime(int viewType, long createTimeNs) {
            ScrapData scrapData = getScrapDataForType(viewType);//通过ViewType查找RecycledViewPool中对应的VH列表
            scrapData.mCreateRunningAverageNs = runningAverage(
                    scrapData.mCreateRunningAverageNs, createTimeNs);//更新列表创建时间
        }
   private ScrapData getScrapDataForType(int viewType) {
            ScrapData scrapData = mScrap.get(viewType);
            if (scrapData == null) {//未查找到ViewType对应的VH列表,则未该ViewType创建对应的VH列表。每个列表的默认大小为5
                scrapData = new ScrapData();
                mScrap.put(viewType, scrapData);
            }
            return scrapData;
        }

7、绑定VH

VH未绑定或者需要更新或者无效状态时,重新绑定VH

 private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
                int position, long deadlineNs) {
            holder.mOwnerRecyclerView = RecyclerView.this;
            final int viewType = holder.getItemViewType();
            long startBindNs = getNanoTime();
            if (deadlineNs != FOREVER_NS
                    && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
                // abort - we have a deadline we can't meet
                return false;
            }
            mAdapter.bindViewHolder(holder, offsetPosition);//调用Adapter的bindViewHolder方法
            long endBindNs = getNanoTime();
            mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);//更新RecycledViewPool中ViewType对应的VH列表绑定时间
            attachAccessibilityDelegateOnBind(holder);
            if (mState.isPreLayout()) {
                holder.mPreLayoutPosition = position;
            }
            return true;
        }

至此,RecyclerView的缓存机制我们的粗略讲完了。如果还是有人觉得一脸懵逼,我建议对着流程图自己去抠几遍源码!

 

发布了29 篇原创文章 · 获赞 3 · 访问量 883

猜你喜欢

转载自blog.csdn.net/LVEfrist/article/details/103733089
今日推荐