源码分析Android源码分析

RecyclerView缓存机制

2020-06-20  本文已影响0人  leap_

在之前的两篇文章介绍了RV的绘制和滑动,留下了两个方法没有具体看,scrollByInternal()
tryGetViewHolderForPositionByDeadline(),本文会补充这两个方法的分析;

RV的四级缓存

Recycler类是RV复用机制的核心实现类,设计了四级缓存,优先级顺序是:Scrap、CacheView、ViewCacheExtension、RecycledViewPool

Recycler类

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

四级缓存:

    public static class RecycledViewPool {
        //  mScrap 的大小
        private static final int DEFAULT_MAX_SCRAP = 5;
        static class ScrapData {
            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
            int mMaxScrap = DEFAULT_MAX_SCRAP;
        }
        SparseArray<ScrapData> mScrap = new SparseArray<>();
    }
RecycledViewPool

RecycledViewPool的本质是一个SparseArray,SparseArray的value是ScrapData(存放VH的ArrayList),缓存池定义了默认的缓存大小DEFAULT_MAX_SCRAP = 5,这个数量不是说整个缓存池只能缓存这多个ViewHolder,而是不同itemType的ViewHolder的list的缓存数量,即mScrap的数量,说明最多只有5组不同类型的mScrapHeap。mMaxScrap = DEFAULT_MAX_SCRAP说明每种不同类型的ViewHolder默认保存5个,当然mMaxScrap的值是可以设置的。

SparseArray分析:避免装箱,节省空间,不需要hash映射

其实,Scrap缓存池不参与滚动的回收复用,CacheView缓存池被称为一级缓存,又因为ViewCacheExtension缓存池是给开发者定义的缓存池,一般不用到,所以RecycledViewPool缓存池被称为二级缓存,那么这样来说实际只有两层缓存。

缓存复用

在RV滑动中,分析了onTouchEvent(),在Action_MOVE的时候会调用scrollByInternal()去滑动

boolean scrollByInternal(int x, int y, MotionEvent ev) {
    ...
            if (x != 0) {
                consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                unconsumedX = x - consumedX;
            }
            if (y != 0) {
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                unconsumedY = y - consumedY;
            }
...
 }

ScrollByInternal()会将滑动操作托付给LayoutManager,LayoutManager会调用自己的fill()处理,这个方法源码注释描述为The magic functions

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
...

            recycleByLayoutState(recycler, layoutState);

            layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
    }
layoutChunk():

这个方法在绘制篇已经分析过,在绘制时step2()中调用LayoutManger的onLayoutChildren()


上面是关于绘制时候的分析,下面补充一下复用的逻辑next();

注意:新版本中,tryGetViewHolderForPositionByDeadline() 改名为 getViewForPosition()

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

        }


       View getViewForPosition(int position, boolean dryRun) {
...
               // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                holder = getChangedScrapViewForPosition(position);
                fromScrap = holder != null;
            }

            // 1) Find from scrap by position
            if (holder == null) {
                holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);

                // 2) Find from scrap via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                  
                  //  自定义缓存获取
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);

                    //  调用adapter去创建新的ViewHolder
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
        }
next() 获取viewHolder

至此,RV的复用机制已经分析的很清楚了,下面看回收机制

recycleByLayoutState()回收的入口方法:


recycleByLayoutState()的代码就不贴了,最后它调用了回收的核心方法:recyclerViewHolderInternal():
      void recycleViewHolderInternal(ViewHolder holder) {
...
                    if (cachedViewSize < mViewCacheMax) {       
                        mCachedViews.add(holder);
                        cached = true;
                    }
                }
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder);
                    recycled = true;
                }
...
        }

        void addViewHolderToRecycledViewPool(ViewHolder holder) {
            ViewCompat.setAccessibilityDelegate(holder.itemView, null);
            dispatchViewRecycled(holder);
            holder.mOwnerRecyclerView = null;
            getRecycledViewPool().putRecycledView(holder);
        }

上面的代码我们可以看到在RV滑动的时候,真正回收的只有两级缓存,mCachedViewsRecycledViewPool

至此,RV的两级回收和复用已经分析完毕,下面看一下第一级缓存的回收工作, 第一级缓存Scrap的回收是在绘制的时候工作的;

Scarp回收和复用

在绘制的时候,Step2除了调用fill()去对chail进行测量和布局,在此之前还计算了锚点和调用了detachAndScrapAttachedViews(),这些操作都是由LayoutManager完成的,RV28.0把step1,step2,step3移除了,但是这些实现的思路任然是一样的;

        public void detachAndScrapAttachedViews(Recycler recycler) {
            final int childCount = getChildCount();
            for (int i = childCount - 1; i >= 0; i--) {
                final View v = getChildAt(i);
                scrapOrRecycleView(recycler, i, v);
            }
        }


        private void scrapOrRecycleView(Recycler recycler, int index, View view) {
...
            if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !viewHolder.isChanged() &&
                    !mRecyclerView.mAdapter.hasStableIds()) {
                removeViewAt(index);
                recycler.recycleViewHolderInternal(viewHolder);
            } else {
                detachViewAt(index);
                recycler.scrapView(view);
            }
        }

       void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            holder.setScrapContainer(this);
            if (!holder.isChanged() || !supportsChangeAnimations()) {
                if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
                    throw new IllegalArgumentException("Called scrap view with an invalid view."
                            + " Invalid views cannot be reused from scrap, they should rebound from"
                            + " recycler pool.");
                }
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                mChangedScrap.add(holder);
            }
        }

在这里我们看到了Scrap缓存的核心回收方法scrapView(),通过viewHolder的状态,判断当前是在mAttachedScrap回收,还是在mChangedScrap回收

复用的话也是在上文的核心复用方法里面老版本是tryGetViewHolderForPositionByDeadline(),新版本是getViewForPosition()

上一篇 下一篇

猜你喜欢

热点阅读