RecycleBin缓存机制

用处:AbsListView的用来缓存重用的View,而缓存是通过RecycleBin来实现的。
RecycleBin有两个等级的存储:ActiveViews and ScrapViews。ActiveViews是开始出现在屏幕上的view,这些view会显示当前的内容。在布局将要滑出屏幕的时候,
ActiveViews将要变成ScrapViews。ScrapViews存储用过的view,这些旧view可以通过Adapter来复用以避免多余的分配视图。

RecycleBin的成员变量介绍

private int mFirstActivePosition;  //存储在ActiveViews中第一个视图的postion。
private View[] mActiveViews = new View[0]; //mActiveViews代表连续的一排视图,开始出现在屏幕中的view会存储在这个数组中,滑出屏幕的时候会移除出数组
private ArrayList[] mScrapViews;  //缓存起来的视图,它们可以被adapter当做convert view 使用。
private int mViewTypeCount;          //代表视图类型的数量(一个列表型的展示界面,不一定都是一种layout,可能会有多种)
private ArrayList mCurrentScrap;    //mScrapViews的第一个元素的值
private SparseArray mTransientStateViews;    //数据集不变的具有TransientState的View数组
private LongSparseArray mTransientStateViewsById;    //具有固定ID的具有TransientState的View数组
private ArrayList mSkippedScrap;    //具有TransientState状态但是不满足以上任意一种状态的View数组,不予缓存

成员变量中  mTransientStateViews 和 mTransientStateViewsById 需要对 StableId 和 Transient State有所理解,

StableId:就是所有的Item具有相同的ID,也就是所有的Item都相同,通过复写BaseAdapter中的hasStableIds可以进行设置,默认为false。

Transient State: 在这里面有一个Transient  State,是View的一个属性,说的是View伴随有动画之类的效果,
对于这种状态的View只有跟Adapter绑定的数据源没有发生变化或者View有相同的ID的时候才能进行缓存复用,
因为这两种情况下Item要么数据不变,不用重新绑定数据,要么View不变,不需要重新创建。

任何一个缓存机制都离不开 初始化清空缓存,因此按照这个顺序来了解下RecycleBin的方法。

 

1.初始化

AbsListView

    /**
     * The data set used to store unused views that should be reused during the next layout
     * to avoid creating new ones
     */
    final RecycleBin mRecycler = new RecycleBin();

RecycleBin的setViewTypeCount方法

        public void setViewTypeCount(int viewTypeCount) {
            if (viewTypeCount < 1) {
                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
            }
            //noinspection unchecked
            ArrayList[] scrapViews = new ArrayList[viewTypeCount];
            for (int i = 0; i < viewTypeCount; i++) {
                scrapViews[i] = new ArrayList();
            }
            mViewTypeCount = viewTypeCount;
            mCurrentScrap = scrapViews[0];
            mScrapViews = scrapViews;
        }

RecycleBin没有构造方法,它部分成员变量的初始化在setViewTypeCount方法中。

这个方法是设置显示的视图类型数量,如果有n种视图类型,那么mScrapViews的length就是n,分别保存一个ArrayList。默认mCurrentScrap,就是数组第一个ArrayList。

在ListView的setAdapter中调用此方法,如下:

    @Override
    public void setAdapter(ListAdapter adapter) {

        ... ...        

        if (mAdapter != null) {
            ... ...
            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
            ... ...
        } else {
            mAreAllItemsSelectable = true;
            checkFocus();
            // Nothing selected
            checkSelectionChanged();
        }

        requestLayout();
    }

 

2.存

addScrapView方法(保存需要缓存的view)

        /**
         * Puts a view into the list of scrap views.
         * 

* If the list data hasn't changed or the adapter has stable IDs, views * with transient state will be preserved for later retrieval. * * @param scrap The view to add * @param position The view's position within its parent */ void addScrapView(View scrap, int position) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); if (lp == null) { // Can't recycle, but we don't know anything about the view. // Ignore it completely. return; } lp.scrappedFromPosition = position; // Remove but don't scrap header or footer views, or views that // should otherwise not be recycled. final int viewType = lp.viewType; // viewType >= 0 进行缓存,否则进入if语句 if (!shouldRecycleViewType(viewType)) { // Can't recycle. If it's not a header or footer, which have // special handling and should be ignored, then skip the scrap // heap and we'll fully detach the view later. // viewType类型不是 Header或者Footer 那么item可能是一个ignore,所以放入到过滤 数组不进行缓存 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { getSkippedScrap().add(scrap); } return; } scrap.dispatchStartTemporaryDetach(); // The the accessibility state of the view may change while temporary // detached and we do not allow detached views to fire accessibility // events. So we are announcing that the subtree changed giving a chance // to clients holding on to a view in this subtree to refresh it. notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); // Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); //add的view有transient state属性进入if语句 if (scrapHasTransientState) { if (mAdapter != null && mAdapterHasStableIds) { // If the adapter has stable IDs, we can reuse the view for // the same data. // 如果adapter的item有相同的id,可以用相同的数据重用view if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<>(); } mTransientStateViewsById.put(lp.itemId, scrap); } else if (!mDataChanged) { // If the data hasn't changed, we can reuse the views at // their old positions. //如果数据源没有发生变换,那么可以用之前的postion数值重用view if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<>(); } mTransientStateViews.put(position, scrap); } else { // Otherwise, we'll have to remove the view and start over. //否则,我们不缓存view,把它放入过滤数组。 getSkippedScrap().add(scrap); } } else { //根据viewType将view添加到相应的scrapView数组中 if (mViewTypeCount == 1) { mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } }

 

fillActiveViews方法(填充在屏幕中要显示的view)

        /**
         * Fill ActiveViews with all of the children of the AbsListView.
         *    
         * @param childCount The minimum number of views mActiveViews should hold
         * @param firstActivePosition The position of the first view that will be stored in
         *        mActiveViews
         */
        void fillActiveViews(int childCount, int firstActivePosition) {
            //判断是否扩容mActiveViues数组
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;

            //noinspection MismatchedReadAndWriteOfArray
            final View[] activeViews = mActiveViews;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
                // Don't put header or footer views into the scrap heap
                if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                    // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
                    //        However, we will NOT place them into scrap views.
                    //将非Header 和 Footer view添加到activeiViews数组中
                    activeViews[i] = child;
                    // Remember the position so that setupChild() doesn't reset state.
                    //区别scarp view 的layoutparams的scrappedFromPosition 属性
                    //在addScrapView方法中scrappedFromPosition 是 position,而这里是有偏移量的
                    lp.scrappedFromPosition = firstActivePosition + i;
                }
            }
        }

fillActiveViews方法是在ListView的layoutChildren的方法中被调用的,layoutChildren是AbsListView中的抽象方法,是在AbsListView的onLayout方法中调用的,因此fillActiveViews是发生在AbsListView子类执行onLayout方法的时候

 

reclaimScrapViews方法(将所有的scrap view 放入到给定的的list中)

        /**
         * Puts all views in the scrap heap into the supplied list.
         */
        void reclaimScrapViews(List views) {
            if (mViewTypeCount == 1) {
                views.addAll(mCurrentScrap);
            } else {
                final int viewTypeCount = mViewTypeCount;
                final ArrayList[] scrapViews = mScrapViews;
                for (int i = 0; i < viewTypeCount; ++i) {
                    final ArrayList scrapPile = scrapViews[i];
                    views.addAll(scrapPile);
                }
            }
        }

 

 

3.取

getActiveView

        /**
         * Get the view corresponding to the specified position. The view will be removed from
         * mActiveViews if it is found.
         *
         * @param position The position to look up in mActiveViews
         * @return The view if it is found, null otherwise
         * 根据postion获取显示在屏幕上的view,如果view存在,则将它移除出Active View数组
         */
        View getActiveView(int position) {
            int index = position - mFirstActivePosition;
            final View[] activeViews = mActiveViews;
            if (index >=0 && index < activeViews.length) {
                final View match = activeViews[index];
                activeViews[index] = null;
                return match;
            }
            return null;
        }

该方法在ListView的makeAndAddView方法中被调用。

    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        if (!mDataChanged) {
            // Try to use an existing view for this position.
            final View activeView = mRecycler.getActiveView(position);
            if (activeView != null) {
                // Found it. We're reusing an existing child, so it just needs
                // to be positioned like a scrap view.
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }

        // Make a new view for this position, or convert an unused view if
        // possible.
        final View child = obtainView(position, mIsScrap);

        // This needs to be positioned and measured.
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

 

getScrapView

        /**
         * @return A view from the ScrapViews collection. These are unordered.
         * 根据position从ScrapView中获取对应的View
         */
        View getScrapView(int position) {
            final int whichScrap = mAdapter.getItemViewType(position);
            if (whichScrap < 0) {
                return null;
            }
            if (mViewTypeCount == 1) {
                return retrieveFromScrap(mCurrentScrap, position);
            } else if (whichScrap < mScrapViews.length) {
                return retrieveFromScrap(mScrapViews[whichScrap], position);
            }
            return null;
        }

该方法在AbsListView的obtainView方法中被调用。

 

getSkippedScrap

        private ArrayList getSkippedScrap() {
            if (mSkippedScrap == null) {
                mSkippedScrap = new ArrayList<>();
            }
            return mSkippedScrap;
        }

该方法是在RecycleBin执行addScrapView方法时被调用。看来是根据某个逻辑判断某个View不会被加入缓存数组中,就会添加到这个废弃的数组中。

 

getTransientStateView

        // 获取具有 transient state 状态的view
        View getTransientStateView(int position) {
            if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
                long id = mAdapter.getItemId(position);
                View result = mTransientStateViewsById.get(id);
                mTransientStateViewsById.remove(id);
                return result;
            }
            if (mTransientStateViews != null) {
                final int index = mTransientStateViews.indexOfKey(position);
                if (index >= 0) {
                    View result = mTransientStateViews.valueAt(index);
                    mTransientStateViews.removeAt(index);
                    return result;
                }
            }
            return null;
        }

该方法是在AbsListView的obtainView的方法中调用的。

 

4.清空缓存

clear

        /**
         * Clears the scrap heap.
         * 清空所有缓存起来的view
         */
        void clear() {
            if (mViewTypeCount == 1) {
                final ArrayList scrap = mCurrentScrap;
                clearScrap(scrap);
            } else {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList scrap = mScrapViews[i];
                    clearScrap(scrap);
                }
            }

            clearTransientStateViews();
        }

 

clearScrap

        //清空scrap view 数组
        private void clearScrap(final ArrayList scrap) {
            final int scrapCount = scrap.size();
            for (int j = 0; j < scrapCount; j++) {
                removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
            }
        }

 

clearTransientStateViews

        /**
         * Dumps and fully detaches any currently saved views with transient
         * state.
         * 清空所有transient state view
         */
        void clearTransientStateViews() {
            final SparseArray viewsByPos = mTransientStateViews;
            if (viewsByPos != null) {
                final int N = viewsByPos.size();
                for (int i = 0; i < N; i++) {
                    removeDetachedView(viewsByPos.valueAt(i), false);
                }
                viewsByPos.clear();
            }

            final LongSparseArray viewsById = mTransientStateViewsById;
            if (viewsById != null) {
                final int N = viewsById.size();
                for (int i = 0; i < N; i++) {
                    removeDetachedView(viewsById.valueAt(i), false);
                }
                viewsById.clear();
            }
        }

 

pruneScrapViews

        /**
         * Makes sure that the size of mScrapViews does not exceed the size of
         * mActiveViews, which can happen if an adapter does not recycle its
         * views. Removes cached transient state views that no longer have
         * transient state.
         */
        private void pruneScrapViews() {
            final int maxViews = mActiveViews.length;
            final int viewTypeCount = mViewTypeCount;
            final ArrayList[] scrapViews = mScrapViews;
            for (int i = 0; i < viewTypeCount; ++i) {
                final ArrayList scrapPile = scrapViews[i];
                int size = scrapPile.size();
                while (size > maxViews) {
                    scrapPile.remove(--size);
                }
            }

            final SparseArray transViewsByPos = mTransientStateViews;
            if (transViewsByPos != null) {
                for (int i = 0; i < transViewsByPos.size(); i++) {
                    final View v = transViewsByPos.valueAt(i);
                    if (!v.hasTransientState()) {
                        removeDetachedView(v, false);
                        transViewsByPos.removeAt(i);
                        i--;
                    }
                }
            }

            final LongSparseArray transViewsById = mTransientStateViewsById;
            if (transViewsById != null) {
                for (int i = 0; i < transViewsById.size(); i++) {
                    final View v = transViewsById.valueAt(i);
                    if (!v.hasTransientState()) {
                        removeDetachedView(v, false);
                        transViewsById.removeAt(i);
                        i--;
                    }
                }
            }
        }

该方法是在RecycleBin的scrapActiveViews方法中调用的,scrapActiveViews(在ListView的layoutChildren中被调用)的作用是处理将没有用到的Active View变成Scrap View的逻辑,因此pruneScrapViews处理了两件事,

1.判断用来缓存view的数组(ScrapView)的size是否大于Active View数组的size,如果大于则删除部分缓存。

2.防止缓存一个view在多个地方,需要判断view是否具有transient state,如果不具有则将它移除出transient state的数组。

 

markChildrenDirty

public void markChildrenDirty() {
            if (mViewTypeCount == 1) {
                final ArrayList scrap = mCurrentScrap;
                final int scrapCount = scrap.size();
                for (int i = 0; i < scrapCount; i++) {
                    scrap.get(i).forceLayout();
                }
            } else {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList scrap = mScrapViews[i];
                    final int scrapCount = scrap.size();
                    for (int j = 0; j < scrapCount; j++) {
                        scrap.get(j).forceLayout();
                    }
                }
            }
            if (mTransientStateViews != null) {
                final int count = mTransientStateViews.size();
                for (int i = 0; i < count; i++) {
                    mTransientStateViews.valueAt(i).forceLayout();
                }
            }
            if (mTransientStateViewsById != null) {
                final int count = mTransientStateViewsById.size();
                for (int i = 0; i < count; i++) {
                    mTransientStateViewsById.valueAt(i).forceLayout();
                }
            }
        }

这个方法是将缓存中的view调用它的forceLayout方法,forceLayout只是会在下一个layout的过程中强制它自己进行onMeasure和onLayout方法,与forceLayout区别的是requestLayout方法,requestLayout方法还会调用parent的onMeasure和onLayout方法。为什么是会在下一个layout过程中,那是因为forceLayout只是标记一个标识,而不会调用任何的方法。调用markChildrenDirty的方法是在AbsListView的onLayout方法中,并且是当ListView的size发生变化的时候,layoutChildren方法发生之前触发。

 

你可能感兴趣的:(Android源码)