由于ListView使用懒加载的机制,只加载当前屏幕中可见条目的视图,处于不可见的条目是不会被加载的。在动态滑动过程中,屏幕的

可见元素不断的发生变化,需要不断的创建需要显示在当前屏幕中的条目元素,而通常创建条目view集合的方法为inflate xml文件,这是

一个比较耗时的操作。所以谷歌的工程师使用了条目convertView的缓存机制,缓存机制的实现类为RecycleBin,它是作为AbsListView的内

部类存在的。分析完RecycleBin,我们基本上就掌握了缓存机制的原理。

    

      一.二级缓存池的初始化

 

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

     这个方法为初始化缓存池。

     缓存池其实就是一个ArrayList<View>。

     scrapViews 缓冲池集合,这是一个数组,数组的每一个元素为ArrayList<View>。所以缓存池不只一个,缓存池的个数为viewTypeCount

     这个方法只有一个地方调用,即ListView的setAdapter方法中,如下:

 

public void setAdapter(ListAdapter adapter) {
          ............
          mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
     }

 

     缓存池集合在整个adapter的生命周期中size是不会改变的,adapter.notifyDatasetChanged不会改变缓存池集合的size.

     mAdapter.getViewTypeCount()的返回值必须唯一,如果在setAdaper中返回值为2,adapter.notifyDatasetChanged之后返回值改为3,

     这时数据确实发生了改变,则必然会抛出数组下标越界异常,具体抛出在layoutChildren()方法中的

     recycleBin.addScrapView(getChildAt(i));,因为add缓冲池方法不会检查数组角标。

 

 

     二.二级缓存池的清理

void clear() {
            if (mViewTypeCount == 1) {
                final ArrayList<View> scrap = mCurrentScrap;
                final int scrapCount = scrap.size();
                for (int i = 0; i < scrapCount; i++) {
                    removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
                }
            } else {
                final int typeCount = mViewTypeCount;
                for (int i = 0; i < typeCount; i++) {
                    final ArrayList<View> scrap = mScrapViews[i];
                    final int scrapCount = scrap.size();
                    for (int j = 0; j < scrapCount; j++) {
                        removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
                    }
                }
            }
      }

      清空缓存池集合

      默认情况下,mViewTypeCount为1,缓冲池为mCurrentScrap。如果重写了adapter的getViewTypeCount,则所有缓存池都会清空。

 

 

      三.从二级缓存池查找元素

   

View getScrapView(int position) {
            ArrayList<View> scrapViews;
            if (mViewTypeCount == 1) {
                scrapViews = mCurrentScrap;
                int size = scrapViews.size();
                if (size > 0) {
                    return scrapViews.remove(size - 1);
                } else {
                    return null;
                }
            } else {
                int whichScrap = mAdapter.getItemViewType(position);
                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
                    scrapViews = mScrapViews[whichScrap];
                    int size = scrapViews.size();
                    if (size > 0) {
                        return scrapViews.remove(size - 1);
                    }
                }
            }
            return null;
        }

     根据position从缓存池查找对应的缓存itemView。查找时先找缓存池,然后再从缓存池中取出最后一个元素。

     查找对应的缓存池是根据scrapViews数组的下标直接映射的。下标是通过mAdapter.getItemViewType(position)

     取得的。所以重写getItemViewType方法时, type的返回值必须在[0.....getViewTypeCount]之间,其他的type

     值将导致某些类型的条目不被复用,因为type的值不在数组下标范围内,不能取出相应的缓存池。

     

 

     四.添加元素到二级缓存池

 

void addScrapView(View scrap) {
            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
            if (lp == null) {
                return;
            }
 
            // Don't put header or footer views or views that should be ignored
            // into the scrap heap
            int viewType = lp.viewType;
            if (!shouldRecycleViewType(viewType)) {
                return;
            }
 
            if (mViewTypeCount == 1) {
                mCurrentScrap.add(scrap);
            } else {
                mScrapViews[viewType].add(scrap);
            }
 
            if (mRecyclerListener != null) {
                mRecyclerListener.onMovedToScrapHeap(scrap);
            }
     }

 

     将某一个条目添加到缓存池中。

     添加时先找viewType,在找到对应的缓存池添加。

     这个地方没有做数组下标越界检查,如果type值大于缓存池集合的大小,则会抛出异常。

 

 

     五.一级缓存池分析

 

   

void fillActiveViews(int childCount, int firstActivePosition) {
            if (mActiveViews.length < childCount) {
                mActiveViews = new View[childCount];
            }
            mFirstActivePosition = firstActivePosition;
 
            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 != AdapterView.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.
                    activeViews[i] = child;
                }
            }
        }

       当前屏幕中的视图缓存集合。

       mActiveViews为view的数组,只保存当前屏幕中的视图条目。

       这个方法在layoutChildren中(dataChanged == false)时调用。recycleBin.fillActiveViews(childCount, firstPosition);

       如果adaper中的数据没有发生改变,则ListView在layout时会保存所有当前屏幕中的元素。以备下次界面更新时使用。

 

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

       

       获取当前屏幕中的view视图。如果较上一次比较,adapter中数据没有发生改变,在创建itemView时,会优先从activeViews池中找

       是否存在已有的条目view,如果有则直接使用,连setData的过程都没有了。如下:

if (!mDataChanged) {
            // Try to use an exsiting view for this position
            child = mRecycler.getActiveView(position);
            if (child != null) {
                if (ViewDebug.TRACE_RECYCLER) {
                    ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
                            position, getChildCount());
                }
 
                // Found it -- we're using an existing child
                // This just needs to be positioned
                setupChild(child, position, y, flow, childrenLeft, selected, true);
 
                return child;
            }
        }

 

     

// Make a new view for this position, or convert an unused view if possible
        child = obtainVew(position);

 

       由于setupChild方法没有调用adaper.getview方法,所以这种情况下根本就没有调用getview的重写方法。

       obtainVew(position);这种方法生成的view是调用getView方法的。

 

总结:

        1.ListView存在两个缓存池系统,一级缓存复用时不需要调用getView,二级缓存需要调用getView.

 

        2.ListView的缓存并不是像某些博客上面写的循环利用图那样简单,向下滑动时,上面的Item被下面的再次使用,这是一种误导。

          实际情况是一个缓存堆栈,后进先出,上面消失的item不一定就是被下面的再次使用了,下面要使用的只是缓存堆栈的最后一个,

          和上面消失的没有直接联系。

 

        3.ListView的二级缓存可以缓存不同类别的View。即使某个View被滑出屏幕很远了,回来时仍然可以复用。