RecylerView 复用问题

ListView的复用大家都应该相当熟悉了,先来看一张图片。


RecylerView 复用问题_第1张图片

对于一个ListView来说,如果屏幕可以显示的Item是n个,那么使用convertView复用后,整个过程中最多也就创建n+1个Item,其它就都是复用的,当然了,如果ViewType是多种,那么数量肯定会多一些。但是原理都是一样的。
RecylerView 是Google推出的一个更加侧重复用的列表组件,很多人的第一感觉就是它跟ListView的复用应该是一样的,只不过更加方便了。建议大家自己写一个Demo项目来运行一下,会发现 RecylerView 调用 onCreateViewHolder 的次数完全不是我们预想的那样,是n+1。也就是说它创建的Item个数变多了,一般是n+4,这是什么原因呢。又能如何设置修改这个效果呢。
问题的关键就是:

 /**
     * Set the number of offscreen views to retain before adding them to the potentially shared
     * {@link #getRecycledViewPool() recycled view pool}.
     *
     * 

The offscreen view cache stays aware of changes in the attached adapter, allowing * a LayoutManager to reuse those views unmodified without needing to return to the adapter * to rebind them.

* * @param size Number of views to cache offscreen before returning them to the general * recycled view pool */ public void setItemViewCacheSize(int size) { mRecycler.setViewCacheSize(size); }

但是如果只设置了这个为0,会发现数理只是减少了两个还是不对。我们继续来查看源码。mRecycler 是 RecylerView 的内部类 Recycler对象。

        private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
        int mViewCacheMax = DEFAULT_CACHE_SIZE;

        static final int DEFAULT_CACHE_SIZE = 2;

        /**
         * Set the maximum number of detached, valid views we should retain for later use.
         *
         * @param viewCount Number of views to keep before sending views to the shared pool
         */
        
        public void setViewCacheSize(int viewCount) {
            
            mRequestedCacheMax = viewCount;
            
            updateViewCacheSize();
        }

        void updateViewCacheSize() {
            
            int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
            mViewCacheMax = mRequestedCacheMax + extraCache;

            // first, try the views that can be recycled
            for (int i = mCachedViews.size() - 1;
                    i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
                recycleCachedViewAt(i);
            }
        }

mLayout就是我们的LayoutManager。

  /**
         * Written by {@link GapWorker} when prefetches occur to track largest number of view ever
         * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or
         * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call.
         *
         * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)},
         * will be reset upon layout to prevent initial prefetches (often large, since they're
         * proportional to expected child count) from expanding cache permanently.
         */
        int mPrefetchMaxCountObserved;

首先我们确定缓存的数目也受这个变量的影响,但是它的初始值是0,为什么实际效果中多了一个呢。通过注释我们可以看到是 GapWorker 这个类中有方法会修改它。

void collectPrefetchPositionsFromView(RecyclerView view, boolean nested) {
            mCount = 0;
            if (mPrefetchArray != null) {
                Arrays.fill(mPrefetchArray, -1);
            }

            final RecyclerView.LayoutManager layout = view.mLayout;
            if (view.mAdapter != null
                    && layout != null
                    && layout.isItemPrefetchEnabled()) {
                    
                if (nested) {
                    // nested prefetch, only if no adapter updates pending. Note: we don't query
                    // view.hasPendingAdapterUpdates(), as first layout may not have occurred
                    if (!view.mAdapterHelper.hasPendingUpdates()) {
                        layout.collectInitialPrefetchPositions(view.mAdapter.getItemCount(), this);
                        
                    }
                } else {
                    // momentum based prefetch, only if we trust current child/adapter state
                    if (!view.hasPendingAdapterUpdates()) {
                        layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy,
                                view.mState, this);
                                 
                    }
                }
                
                
                if (mCount > layout.mPrefetchMaxCountObserved) {
                    layout.mPrefetchMaxCountObserved = mCount;
                    layout.mPrefetchMaxObservedInInitialPrefetch = nested;
                    view.mRecycler.updateViewCacheSize();
                }
            }
        }
        
     @Override
    public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
            LayoutPrefetchRegistry layoutPrefetchRegistry) {
        int delta = (mOrientation == HORIZONTAL) ? dx : dy;
        if (getChildCount() == 0 || delta == 0) {
            // can't support this scroll, so don't bother prefetching
            return;
        }

        ensureLayoutState();
        final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        final int absDy = Math.abs(delta);
        updateLayoutState(layoutDirection, absDy, true, state);
        collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
    }
    void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
            LayoutPrefetchRegistry layoutPrefetchRegistry) {
        final int pos = layoutState.mCurrentPosition;
        if (pos >= 0 && pos < state.getItemCount()) {
        
            layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset));
        }
    }
         @Override
        public void addPosition(int layoutPosition, int pixelDistance) {
            if (layoutPosition < 0) {
                throw new IllegalArgumentException("Layout positions must be non-negative");
            }

            if (pixelDistance < 0) {
                throw new IllegalArgumentException("Pixel distance must be non-negative");
            }

            // allocate or expand array as needed, doubling when needed
            final int storagePosition = mCount * 2;
            if (mPrefetchArray == null) {
                mPrefetchArray = new int[4];
                Arrays.fill(mPrefetchArray, -1);
            } else if (storagePosition >= mPrefetchArray.length) {
                final int[] oldArray = mPrefetchArray;
                mPrefetchArray = new int[storagePosition * 2];
                System.arraycopy(oldArray, 0, mPrefetchArray, 0, oldArray.length);
            }
 
            // add position
            mPrefetchArray[storagePosition] = layoutPosition;
            mPrefetchArray[storagePosition + 1] = pixelDistance;
            
            mCount++;
        }

最后mCount变成1,也就是说把mPrefetchMaxCountObserved变成1,ViewCacheSize变成3了。
那怎么把 RecylerView 变得和 ListView一样也就很简单了,只需要两行代码。

         recylerView.setItemViewCacheSize(0);
        layout.setItemPrefetchEnabled(false);

最后谈下我的理解。可以这样来理解 RecylerView 的缓存机制。 首先它支持预加载,像ViewPager一样(ViewPager的那些事——你真了解预加载机制吗?),最少需要一个提前量。然后它的后边不是马上回收复用,而是维护一个缓存池,这个池子的大小可以设置,只有当池子满了之后再加入新的才会开始回收重用。

你可能感兴趣的:(RecylerView 复用问题)