RecyclerView缓存机制源码学习

RecyclerView缓存数据结构

RecyclerView相比ListView缓存机制更为复杂些,它一共有四级缓存,RecyclerView中缓存相关的代码在它的内部类 Recycler里,先来看一下Recycler中相关的数据结构

第一级:mChangedScrap(离屏) 与 mAttachedScrap(未离屏) ---》 回收相关方法:recycler.scrapView(view);

第二级:mCachedViews---》 默认大小为2 ---》 回收相关方法:recycler.recycleViewHolderInternal()

第三级:mViewCacheExtension ---》自定义缓存,一般不用它

第四级:RecyclerViewPool 缓存池---》 默认大小5(同一个viewType的集合大小为5)

public final class Recycler {
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        ArrayList<ViewHolder> mChangedScrap = null;
        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
        private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
        //mCachedViews的默认大小为2
        int mViewCacheMax = DEFAULT_CACHE_SIZE;
        RecycledViewPool mRecyclerPool;
        private ViewCacheExtension mViewCacheExtension;//自定义缓存
        static final int DEFAULT_CACHE_SIZE = 2;

下面根据滑动跟踪下RecyclerView缓存相关的源码,首先看RecyclerView#onTouchEvent中ACTION_MOVE事件的处理

    @Override
    public boolean onTouchEvent(MotionEvent e) {
       ......
        switch (action) {
            ......
            case MotionEvent.ACTION_MOVE: {
                final int index = e.findPointerIndex(mScrollPointerId);
                ......
                final int x = (int) (e.getX(index) + 0.5f);
                final int y = (int) (e.getY(index) + 0.5f);
                int dx = mLastTouchX - x;
                int dy = mLastTouchY - y;
                ......
                if (mScrollState == SCROLL_STATE_DRAGGING) {
                    ......
                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            e)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    ......
                }
            } break;
            case MotionEvent.ACTION_POINTER_UP: {
                onPointerUp(e);
            } break;

 

    boolean scrollByInternal(int x, int y, MotionEvent ev) {
        ......
        if (mAdapter != null) {
            mReusableIntPair[0] = 0;
            mReusableIntPair[1] = 0;
            scrollStep(x, y, mReusableIntPair);//处理滑动
            consumedX = mReusableIntPair[0];
            consumedY = mReusableIntPair[1];
            unconsumedX = x - consumedX;
            unconsumedY = y - consumedY;
        }
    }

    void scrollStep(int dx, int dy, @Nullable int[] consumed) {
        ......
        if (dx != 0) {
            consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);//处理水平滑动
        }
        if (dy != 0) {
            consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);//处理垂直滑动
        }
        ......
    }

其中RecyclerView中mLayout定义为LayoutManager,这里我们直接看LinearLayoutManger的实现,不管是水平还是垂直滑动,最终都会走到scrollBy()方法,scrollBy中会去调用fill方法,直接看一下fill的处理

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        // max offset we should set is mFastScroll + available
        final int start = layoutState.mAvailable;
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            // TODO ugly bug fix. should not happen
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            //处理回收的逻辑
            recycleByLayoutState(recycler, layoutState);
        }
        int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.beginSection("LLM LayoutChunk");
            }
            //处理复用,只要还有空白区就会调用该方法
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            ......
        return start - layoutState.mAvailable;
    }

RecyclerView的回收机制

    private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
        if (!layoutState.mRecycle || layoutState.mInfinite) {
            return;
        }
        int scrollingOffset = layoutState.mScrollingOffset;
        int noRecycleSpace = layoutState.mNoRecycleSpace;
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
            recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
        } else {
            recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
        }
    }
    //不管是从底部回收还是从顶部回收,回收的逻辑都是一样的
    private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
            int noRecycleSpace) {
        ......
        final int limit = scrollingOffset - noRecycleSpace;
        final int childCount = getChildCount();
        if (mShouldReverseLayout) {
            for (int i = childCount - 1; i >= 0; i--) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedEnd(child) > limit
                        || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                    // stop here
                    recycleChildren(recycler, childCount - 1, i);
                    return;
                }
            }
        } else {
            //可以看到这里遍历了所有的子view,所以回收对象是子view
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedEnd(child) > limit
                        || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                    // stop here
                    recycleChildren(recycler, 0, i);
                    return;
                }
            }
        }
    }

在 recycleChildren()方法中又会去调用 removeAndRecycleViewAt()方法

        public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
            final View view = getChildAt(index);
            removeViewAt(index);//最终会调用ViewGroup的removeViewAt()方法
            recycler.recycleView(view);//真正的回收
        }

RecyclerView回收子view的时候,首先通过getChildAt()方法获取到该子view,然后将该子view从RecyclerView中移除,最后通过Recycler的recycleView方法实现子view的回收

public void recycleView(@NonNull View view) {
            //通过LayoutParams获取到对应子view的ViewHolder
            ViewHolder holder = getChildViewHolderInt(view);
            if (holder.isTmpDetached()) {
                removeDetachedView(view, false);
            }
            if (holder.isScrap()) {
                holder.unScrap();
            } else if (holder.wasReturnedFromScrap()) {
                holder.clearReturnedFromScrapFlag();
            }
            //回收对应子view的ViewHolder
            recycleViewHolderInternal(holder);
            ......
}

主要的回收逻辑在 recycleViewHolderInternal()方法中,RecyclerView回收的对象为其子View对应的 ViewHolder,也即是RecyclerView缓存回收的对象就是 ViewHolder,这点和ListView不同,想搞清楚回收机制首先我们需要了解回收机制里涉及到的两个重要的数据结构: mCachedViews 和 RecycledViewPool (mScrap):

mCachedViews前面已经提到,它是一个存储ViewHolder的ArrayList列表,需要注意的是,这个列表是有最大容量的,它的最大容量在Recycler里也定义了:mViewCacheMax,最大容量值为 2。

RecyclerViewPool中定义了一个类似于Map的结构来存储ViewHolder,它也有最大容量:mMaxScrap,最大容量值为 5,接下来看一下RecyclerViewPool的源码

public static class RecycledViewPool {
        //定义最大容量为 5
        private static final int DEFAULT_MAX_SCRAP = 5;
        //ScrapData封装了一个ArrayList列表 mScrapHeap存储ViewHolder
       //上面定义的最大容量也就是 mScrapHeap的最大容量
        static class ScrapData {
            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
            int mMaxScrap = DEFAULT_MAX_SCRAP;
            long mCreateRunningAverageNs = 0;
            long mBindRunningAverageNs = 0;
        }
        //mScrap 类似于Map,用来存储 ScrapData
        SparseArray<ScrapData> mScrap = new SparseArray<>();
        ......
}

接下来看一下 RecyclerViewPool是如何来保存获取ViewHolder的

        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            //根据ViewHolder的viewType获取mScrap中的ViewHolder列表 mScrapHeap
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }
        private ScrapData getScrapDataForType(int viewType) {
            ScrapData scrapData = mScrap.get(viewType);
            if (scrapData == null) {
                scrapData = new ScrapData();
                mScrap.put(viewType, scrapData);
            }
            return scrapData;
        }

通过putRecyclerView()的源码可以看到RecyclerView是通过 RecyclerViewPool中的 mScrap结构来保存获取ViewHolder的,mScrap是一个类似于Map的集合,它以不同ViewHolder的类型值viewType为key来保存一个Scrap对象,而Scrap其实就是一个ViewHolder的List列表,现在缓存池的结构我们就清楚了:mScrap以 viewType 为key存储了一系列ViewHolder的List列表,并且每个列表最多可以保存 5 个ViewHolder,如果某一个类型的ViewHolder的List列表中元素个数已经达到了最大值5,此时又来了一个相同类型的子View的ViewHolder,那么该ViewHolder将会被遗弃而不能进入缓存池中,所以缓存池中同种类型的ViewHolder的最大容量为5,缓存池的最大容量为N*5,其中N为key的个数,也就是mScrap中viewType的数目,最后根据recycleViewHolderInternal()的源码总结一下RecyclerView的回收机制

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)) {
                    // Retire oldest cached view
                    //回收子veiw的主要逻辑
                    int cachedViewSize = mCachedViews.size();
                    //回收前判断mCachedViews的容量值是否大于最大容量mViewCacheMax
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        //从mCachedViews中取出ViewHolder,添加到缓存池RecyclerViewPool
                        //最后再将该ViewHolder从mCachedViews中移除
                        recycleCachedViewAt(0);//注意:这里的索引值为 0
                        cachedViewSize--;
                    }
                    int targetCacheIndex = cachedViewSize;
                    ......
                    //将对应子view的ViewHolder存入缓存 mCachedViews
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } else {
              ......
        }

RecyclerView的回收机制:首先判断 mCachedViews 里是否已经存满,如果没有存满将直接通过add方法将ViewHolder添加到 mCachedViews中.如果mCachedViews 里已经存满,此时会将 mCachedViews中的第一个元素放进缓存池RecyclerViewPool中,然后将该元素从mCachedViews中移除,所以 mCachedViews 的最大容量始终为 2 并且遵循先进先出的原则。接下来看一下复用。

RecyclerView的复用机制

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
        ......
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        measureChildWithMargins(view, 0, 0);
        ......
    }

通过代码可以看到,复用得到的子view是通过addView()方法来添加的,而复用view的获取是通过 next()函数来实现的,跟进一下 next 函数

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

next函数又是通过 Recycler 的getViewForPosition()方法获取到的view,而复用的核心代码也都在Recycler里,getViewForPosition方法里会调用复用机制的核心方法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()) {
                //通过位置或者id从一级缓存 mChangedScrap 中获取ViewHolder
                holder = getChangedScrapViewForPosition(position);
                fromScrapOrHiddenOrCache = holder != null;
            }
            // 1) Find by position from scrap/hidden list/cache
            //如果上面获取到的ViewHolder为空则继续从一级缓存 mAttachedScrap 中查找
            if (holder == null) {
                //将从一级缓存 mAttachedScrap 中获取ViewHolder
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle holder (and unscrap if relevant) since it can't be used
                        if (!dryRun) {
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            ......
                            //如果获取到的ViewHolder不能使用将对该ViewHolder进行复用处理
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrapOrHiddenOrCache = true;
                    }
                }
            }
            //ViewHolder仍为空则继续从缓存中获取
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                ......
                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2) Find from scrap/cache via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    //先从一级缓存 mAttachedScrap 中查找,如果没有再从二级缓存 mCachedViews 中获取ViewHolder
                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                            type, dryRun);
                   ......
                }
                if (holder == null && mViewCacheExtension != null) {
                    //如果上面的二级缓存 mCachedViews 中返回空则从自定义缓存mViewCacheExtension 中获取
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        ......
                    }
                }
                if (holder == null) { // fallback to pool
                    //二级缓存获取不到从缓存池 RecyclerViewPool 中获取
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        //对从缓存池获取到的ViewHolder进行位置、id等信息的重置
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                if (holder == null) {
                    ......
                    //最后一级缓存仍获取不到,最终将会调用抽象方法 onCreateViewHolder 由用户创建
                    //注意这里传入的第一个参数parent是RecyclerView本身
                    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()) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                //如果ViewHolder还未绑定最终将会调用抽象方法 onBindViewHolder 由用户去绑定
                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
            }
            ......
            return holder;
        }

 

 

 

Guess you like

Origin blog.csdn.net/lollo01/article/details/114298360