深入源码分析RecyclerView缓存复用原理

文章目录

    • 前言
      • 四级缓存
    • 源码分析
      • 缓存
        • 一级缓存(mChangedScrap和mChangedScrap)
        • 二级缓存(mCachedViews)
        • 三级缓存(ViewCacheExtension)
        • 四级缓存(mRecyclerPool)
          • 缓存池mRecyclerPool结构理解
          • 四级缓存简单小结
        • 缓存流程图
      • 复用
        • tryGetViewHolderForPositionByDeadline方法详解
        • 复用流程图
    • 结语

前言

RecyclerView是Android日常开发中经常使用的控件,了解其源码,明白其中的缓存复用机制是十分有必要的;

四级缓存

我们都知道RecyclerView有四级缓存,缓存的都是ViewHolder对象,那都分别对应哪些缓存呢?各自缓存的作用是什么呢?这里先简单总结下:

层级 缓存变量 容量 数据结构 作用
1 mChangedScrap与 mAttachedScrap X ArrayList<ViewHolder> 用来缓存还在屏幕内的ViewHolder
2 mCachedViews 默认为2,可通过调用setViewCacheSize()方法调整 ArrayList<ViewHolder> 用来缓存移除屏幕之外的ViewHolder
3 mViewCacheExtension X 自定义缓存,一般不使用
4 mRecyclerPool 每个itemViewType默认存储5个ViewHolder SparseArray<ScrapData> ViewHolder缓存池,复用时需要重新调用onBindViewHolder

其中ScrapData结构如下:

      static class ScrapData {
            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
        }

源码分析

缓存

我们从RecyclerViewonLayout方法开始跟踪:

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    	...
        dispatchLayout();
   		...
    }

其中dispatchLayout()方法如下:

 void dispatchLayout() {
        if (mAdapter == null) {
            Log.e(TAG, "No adapter attached; skipping layout");
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            return;
        }
        mState.mIsMeasuring = false;
        if (mState.mLayoutStep == State.STEP_START) {
        	//dispatchLayoutStep1()中会做以下几件事:
        	1.处理适配器的更新;
        	2.决定应该运行哪个动画;
        	3.保存有关当前视图的信息;
        	4.运行预测布局并保存其信息;
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            //dispatchLayoutStep2()中会进行实际的布局操作
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // 当宽高改变时,会再次调用 dispatchLayoutStep2()进行重新布局;
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        //dispatchLayoutStep3()处理相关动画
        dispatchLayoutStep3();
    }

这里我们重点关注下 dispatchLayoutStep2()方法;

    private void dispatchLayoutStep2() {
     	...
        mLayout.onLayoutChildren(mRecycler, mState);
        ...
    }

显然,由于dispatchLayoutStep2()主要工作是重新布局,那么肯定要进行子View的布局;
其中 mLayout.onLayoutChildren(mRecycler, mState);调用的是LayoutManager的onLayoutChildren方法,
这里,我们选择LinearLayoutManager来跟进流程;

### LinearLayoutManager.onLayoutChildren
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        ...
        detachAndScrapAttachedViews(recycler);
       	...
    }

onLayoutChildren会调用detachAndScrapAttachedViews(recycler)方法

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

注意这里是倒序遍历,我们重点看看scrapOrRecycleView(recycler, i, v);方法;

   final ViewHolder viewHolder = getChildViewHolderInt(view);
   			//如果viewHolder设置成ignore,则直接返回;
            if (viewHolder.shouldIgnore()) { 
                if (DEBUG) {
                    Log.d(TAG, "ignoring view " + viewHolder);
                }
                return;
            }
            //如果viewHolder数据非法无效 && viewHolder不指向数据集中移除的数据 && adapter没有设置stableId 
            if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                    && !mRecyclerView.mAdapter.hasStableIds()) {
                 //移除当前子View
                removeViewAt(index);
                //里面会调用mCachedViews和mRecyclerPool进行二级和四级缓存(三级缓存为自定义缓存)
                recycler.recycleViewHolderInternal(viewHolder);
            } else {
            	//暂时将View解绑,以便后续可以通过ViewHolder重新绑定复用
                detachViewAt(index);
                //里面会根据条件调用mAttachedScrap或mChangedScrap进行一级缓存;
                recycler.scrapView(view);
                //从消失列表中移除viewHolder
                mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
            }

接下来,我们就重点分别看recycler.scrapView(view) recycler.recycleViewHolderInternal(viewHolder)方法;

一级缓存(mChangedScrap和mChangedScrap)

    void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
                //如果ViewHolder标记为移除或失效的 || ViewHolder没有变化 || item 无动画或动画不复用
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        
               if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
                    throw new IllegalArgumentException("Called scrap view with an invalid view."));
                }
                holder.setScrapContainer(this, false);
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                holder.setScrapContainer(this, true);
                mChangedScrap.add(holder);
            }
        }

从上述代码可以看出:当ViewHolder满足移除或失效||没有变化||没有动画或动画不复用时,缓存到mAttachedScrap集合中,否则缓存到mChangedScrap集合中;

二级缓存(mCachedViews)

        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的大小
                    int cachedViewSize = mCachedViews.size();
                    //如果mCachedViews大小超过或等于默认值2的时候
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);//将下标为0位置的元素从集合中移除,放入到四级缓存mRecyclerPool中
                        cachedViewSize--; //集合大小-1
                    }
					
                    int targetCacheIndex = cachedViewSize; //将cachedViewSize赋值给targetCacheIndex
                    if (ALLOW_THREAD_GAP_WORK
                            && cachedViewSize > 0
                            && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
    				...
    				//缓存新的holder至targetCacheIndex下标中,并设置cached为true
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
               		//没有缓存成功,则放入到四级缓存mRecyclerPool中
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } 
            ...
        }

从上述代码中可以看出:当满足移除屏幕条件时:
1. 当mCachedViews没满时,ViewHolder会直接缓存到mCachedViews中,如果缓存失败,则会缓存到四级缓存mRecyclerPool中;

2. 当mCachedViews满时,会先移除mCachedViews集合中下标为0位置的元素,并将其放置到缓存池mRecyclerPool中;然后将ViewHolder缓存到mCachedViews集合下标为1位置上,如果缓存失败,则会缓存到四级缓存mRecyclerPool中;

三级缓存(ViewCacheExtension)

为用户自定义缓存,可通过自定义ViewCacheExtension,并重写getViewForPositionAndType方法实现;

四级缓存(mRecyclerPool)

从上面二级缓存实现可以看到,会调用addViewHolderToRecycledViewPool(holder, true)实现四级缓存机制;

  void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
  			//1.将viewHolder引用的recyclerView移除掉
            clearNestedRecyclerViewIfNotNested(holder);
           	...
           	//2.移除viewHolder相关监听
            if (dispatchRecycled) {
                dispatchViewRecycled(holder);
            }
            holder.mOwnerRecyclerView = null;
            //3.缓存至mRecyclerPool中;
            getRecycledViewPool().putRecycledView(holder);
        }
		###  getRecycledViewPool().putRecycledView
		
        public void putRecycledView(ViewHolder scrap) {
        	//1.先获取ViewHolder对象的itemViewType
            final int viewType = scrap.getItemViewType();
            //2.根据itemViewType获取对应的ArrayList集合
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            //3.如果集合中已经保存有5个ViewHolder了,那就不再进行缓存操作;
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            //4.已经缓存的有,抛异常
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            //5.将ViewHolder进行`漂白`,清除相关标志、位置信息等等,因此复用缓存池中的ViewHolder需要重新进行绑定操作;
            scrap.resetInternal();
            //6.添加到缓冲池中;
            scrapHeap.add(scrap);
        }
缓存池mRecyclerPool结构理解

深入源码分析RecyclerView缓存复用原理_第1张图片

四级缓存简单小结

根据ViewHolder对应的itemViewType从缓存池中获取对应的ScrapData对象,ScrapData对象内部存储了ArrayList 集合,如果当前集合已满5个,则丢弃ViewHolder不进行缓存,如果集合不满,则先将ViewHolder进行数据漂白,清除相关信息后再添加到缓存集合中!

缓存流程图

深入源码分析RecyclerView缓存复用原理_第2张图片

复用

我们知道当RecyclerView滑动的时候肯定会涉及到ViewHolder的复用,因此我们就以RecyclerViewonTouchEvent(MotionEvent e)方法作为切入点,跟踪整个ViewHolder复用流程

  public boolean onTouchEvent(MotionEvent e) {
  	//这里,我们只关注Move事件,当RecyclerView进行滑动的时候,肯定会执行ACTION_MOVE进行事件消费
    case MotionEvent.ACTION_MOVE: {
               		...
                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            e)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    ...
                }
            } break;
  }

这里重点跟下scrollByInternal()方法;

### RecyclerView.scrollByInternal

 boolean scrollByInternal(int x, int y, MotionEvent ev) {
        ...
        if (mAdapter != null) {
            mReusableIntPair[0] = 0;
            mReusableIntPair[1] = 0;
            //通过调用将 RV 滚动“dx”和“dy”和LayoutManager#scrollHorizo​​ntallyBy()或LayoutManager#scrollVerticallyBy关联起来
            scrollStep(x, y, mReusableIntPair);
            consumedX = mReusableIntPair[0];
            consumedY = mReusableIntPair[1];
            unconsumedX = x - consumedX;
            unconsumedY = y - consumedY;
        }
       ...
    }

scrollByInternal()中通过调用 scrollStep()方法进行复用流程:

### RecyclerView.scrollStep

 void scrollStep(int dx, int dy, @Nullable int[] consumed) {
     	....
        int consumedX = 0;
        int consumedY = 0;
        if (dx != 0) {
            consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
        }
        if (dy != 0) {
            consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
        }
		...
    }

可以看到水平滑动dx或竖直滑动dy最终都是交给mLayout【LayoutManager】进行处理,这里我们随便选择一个方法进去,比如scrollHorizontallyBy

### LinearLayout.scrollHorizontallyBy

   public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            return 0;
        }
        return scrollBy(dx, recycler, state);
    }

继续跟踪scrollBy(),方法如下:

### LinearLayout.scrollBy

  int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
        ...
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
        if (consumed < 0) {
            if (DEBUG) {
                Log.d(TAG, "Don't have any more elements to scroll");
            }
            return 0;
        }
        ...
        return scrolled;
    }

我们重点关注下fill(recycler, mLayoutState, state, false)

### LinearLayout.fill

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
		   
        final int start = layoutState.mAvailable;
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
         	 // TODO ugly bug fix. should not happen 看着像是为了修复某个bug,
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            //1.内部最终会调用RecyclerView.recycleViewHolderInternal方法进行ViewHolder缓存;
            recycleByLayoutState(recycler, layoutState);
        }
       	...
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
          	//2.具体复用逻辑处理;
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.endSection();
            }
            if (layoutChunkResult.mFinished) {
                break;
            }
      

            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                //3.同1处
                recycleByLayoutState(recycler, layoutState);
            }
            if (stopOnFocusable && layoutChunkResult.mFocusable) {
                break;
            }
        }
        if (DEBUG) {
            validateChildOrder();
        }
        return start - layoutState.mAvailable;
    }

可以看到LinearLayout.fill方法中会处理ViewHolder的缓存和复用逻辑,缓存逻辑最终也是通过调用RecyclerView.recycleViewHolderInternal方法实现,这里不再赘述,我们继续跟进复用流程layoutChunk(recycler, state, layoutState, layoutChunkResult)

### LinearLayout.layoutChunk

  void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        //1.获取缓存中的View
        View view = layoutState.next(recycler);
        if (view == null) {
            return;
        }
       ...
       //2.拿到缓存中的view根据条件去做添加重新布局
    }

我们展开看下layoutState.next(recycler)是如何实现ViewHolder复用逻辑的;

### LinearLayout.next

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

跟进下recycler.getViewForPosition(mCurrentPosition)

### RecyclerView.getViewForPosition

   public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
        }
			

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

可以看到会调用tryGetViewHolderForPositionByDeadline方法;

tryGetViewHolderForPositionByDeadline方法详解

   ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
             //1.校验position是否合法,不合法抛异常
            if (position < 0 || position >= mState.getItemCount()) {
                throw new IndexOutOfBoundsException("Invalid item position " + position
                        + "(" + position + "). Item count:" + mState.getItemCount()
                        + exceptionLabel());
            }
            boolean fromScrapOrHiddenOrCache = false;
            ViewHolder holder = null;
            // 2.如果是预布局,调用getChangedScrapViewForPosition获取缓存中的ViewHolder,内部获取的是mChangedScrap集合中的数据,也就是一级缓存
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 3.如果步骤2中获取的holder == null,调用getScrapOrHiddenOrCachedHolderForPosition获取,内部获取的是mAttachedScrap集合或mCachedViews中的数据,对应一级缓存和二级缓存;
            if (holder == null) {
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                      	//4. 检查获取到的holder是否可以用于复用【内部会通过isPreLayout、itemViewType或stableId】
                        if (!dryRun) {
                            //5.上面dryRun传入的是false,因此当获取的holder不可以复用时,会调用recycleViewHolderInternal进行回收
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrapOrHiddenOrCache = true;
                    }
                }
            }
            //如果通过一级缓存还是获取不到holder的话
            if (holder == null) {
             	//获取对应的itemViewType
                final int type = mAdapter.getItemViewType(offsetPosition);
                if (mAdapter.hasStableIds()) {
                   //6.如果adapter设置了StableIds,调用getScrapOrCachedViewForId方法获取缓存holder,内部获取的是mAttachedScrap集合或mCacheViews集合的数据,对应一级或二级缓存
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                  	...
                }
             
                if (holder == null && mViewCacheExtension != null) {
                    //7.如果上述仍旧没从缓存中拿到holder&&用户自定义了ViewCacheExtension(也就是三级缓存)时,尝试从三级缓存中获取View
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                       	...
                    }
                }
                
                if (holder == null) {
                	//8.如果上述过程仍然没拿到holder,则尝试从缓存池中获取,对应四级缓存
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
             
                if (holder == null) {
                    ...
                  	//9.如果所有缓存都没有获取到holder,则调用createViewHolder方法,内部会执行onCreateViewHolder创建新的ViewHolder
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                   	...
                }
            }
			...
            boolean bound = false;
            if (mState.isPreLayout() && holder.isBound()) {
              	//如果是预布局 && holder已绑定,设置对应position
                holder.mPreLayoutPosition = position;
            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
            	//10.如果holder没有绑定 || 需要更新 || 数据不合法,调用tryBindViewHolderByDeadline,内部会调用onBindViewHolder重新进行ViewHolder绑定;
                if (DEBUG && holder.isRemoved()) {
                    throw new IllegalStateException("Removed holder should be bound and it should"
                            + " come here only in pre-layout. Holder: " + holder
                            + exceptionLabel());
                }
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }
			//11.设置ViewHolder对应ViewGroup.LayoutParams
            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;
            rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
            //12.返回从缓存中获取或者重新创建的ViewHolder对象
            return holder;
        }

可以看到tryGetViewHolderForPositionByDeadline是整个VIewHolder复用的核心逻辑,主要做了以下几件事:

  1. 校验position是否合法,不合法抛异常;

  2. 如果是预布局,调用getChangedScrapViewForPosition获取缓存中的ViewHolder,内部获取的是mChangedScrap集合中的数据,对应一级缓存;

  3. 如果步骤2中获取的holder == null,调用getScrapOrHiddenOrCachedHolderForPosition获取,内部获取的是mAttachedScrap集合或mCachedViews集合中的数据,对应一级缓存和二级缓存;

  4. 如果本次步骤3中获取的holder!=null 但不可以复用,则会调用recycleViewHolderInternal进行回收,用于后续复用处理;

  5. 如果adapter设置了StableIds,调用getScrapOrCachedViewForId方法获取缓存holder,内部获取的是mAttachedScrap集合mCachedViews集合中的数据,对应一级缓存和二级缓存;

  6. 如果上述仍旧没从缓存中拿到holder&&用户自定义了ViewCacheExtension(也就是三级缓存)时,尝试从三级缓存中获取View;

  7. 如果上述过程仍然没拿到holder,则尝试从缓存池中获取,对应四级缓存;

  8. 如果所有缓存都没有获取到holder,则调用createViewHolder方法,内部会执行onCreateViewHolder创建新的ViewHolder;

  9. 如果是预布局 && holder已绑定,设置对应position,设置ViewGroup.LayoutParams后直接返回最终的ViewHolder;

  10. 如果holder没有绑定 || 需要更新 || 数据不合法,调用tryBindViewHolderByDeadline,内部会调用onBindViewHolder重新进行ViewHolder绑定,后设置ViewGroup.LayoutParams后返回最终的ViewHolder;

复用流程图

深入源码分析RecyclerView缓存复用原理_第3张图片

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

你可能感兴趣的:(Android学习计划,缓存,java,android)