Android ListView的理解(二) --- Adapter, BaseAdapter, RecycleBin

一、前述

        通常,我们在使用ListView / GridView时,都需要使用Adapter,Adapter有多种,最常用的就是BaseAdapter 和 CursorAdapter了,前者是属于通用的,而后者通常会与数据库一起来使用。

        1. Adapter在源码中是interface,而不是AbstractClass(http://developer.android.com/reference/android/widget/Adapter.html);

        2. BaseAdapter是抽象类,而不是interface;

        3. CursorAdapter也是抽象类,它是继承于BaseAdapter的;

        4. 还有其它Adapter,如ListAdapter等;

        大家看了以上几点后,肯定会有疑问,BaseAdapter与Adapter是怎么联系起来的?

        看过文档或源码的朋友肯定知道:

    /**
     * Common base class of common implementation for an {@link Adapter} that can be
     * used in both {@link ListView} (by implementing the specialized
     * {@link ListAdapter} interface} and {@link Spinner} (by implementing the
     * specialized {@link SpinnerAdapter} interface.
     */
    public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter

        ListAdapter和SpinnerAdapter是两个interface,它们直接继承于Adapter,BaseAdapter是抽象类,可以选择性的实现接口里定义的方法;

        其实,BaseAdapter也是大多数XXXAdapter(不是接口,而是类或抽象类)的parent,只有少数和BaseAdapter一样,如:HeaderViewListAdapter。

二、Adapter

        2.1 Adapter

public interface Adapter {
    /**
     * Register an observer that is called when changes happen to the data used by this adapter.
     *
     * @param observer the object that gets notified when the data set changes.
     */
    void registerDataSetObserver(DataSetObserver observer);

    /**
     * Unregister an observer that has previously been registered with this
     * adapter via {@link #registerDataSetObserver}.
     *
     * @param observer the object to unregister.
     */
    void unregisterDataSetObserver(DataSetObserver observer);

    /**
     * How many items are in the data set represented by this Adapter.
     * 
     * @return Count of items.
     */
    int getCount();   
    
    /**
     * Get the data item associated with the specified position in the data set.
     * 
     * @param position Position of the item whose data we want within the adapter's 
     * data set.
     * @return The data at the specified position.
     */
    Object getItem(int position);
    
    /**
     * Get the row id associated with the specified position in the list.
     * 
     * @param position The position of the item within the adapter's data set whose row id we want.
     * @return The id of the item at the specified position.
     */
    long getItemId(int position);
    
    /**
     * Indicates whether the item ids are stable across changes to the
     * underlying data.
     * 
     * @return True if the same id always refers to the same object.
     */
    boolean hasStableIds();
    
    /**
     * Get a View that displays the data at the specified position in the data set. You can either
     * create a View manually or inflate it from an XML layout file. When the View is inflated, the
     * parent View (GridView, ListView...) will apply default layout parameters unless you use
     * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)}
     * to specify a root view and to prevent attachment to the root.
     * 
     * @param position The position of the item within the adapter's data set of the item whose view
     *        we want.
     * @param convertView The old view to reuse, if possible. Note: You should check that this view
     *        is non-null and of an appropriate type before using. If it is not possible to convert
     *        this view to display the correct data, this method can create a new view.
     *        Heterogeneous lists can specify their number of view types, so that this View is
     *        always of the right type (see {@link #getViewTypeCount()} and
     *        {@link #getItemViewType(int)}).
     * @param parent The parent that this view will eventually be attached to
     * @return A View corresponding to the data at the specified position.
     */
    View getView(int position, View convertView, ViewGroup parent);

    /**
     * An item view type that causes the {@link AdapterView} to ignore the item
     * view. For example, this can be used if the client does not want a
     * particular view to be given for conversion in
     * {@link #getView(int, View, ViewGroup)}.
     * 
     * @see #getItemViewType(int)
     * @see #getViewTypeCount()
     */
    static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
    
    /**
     * Get the type of View that will be created by {@link #getView} for the specified item.
     * 
     * @param position The position of the item within the adapter's data set whose view type we
     *        want.
     * @return An integer representing the type of View. Two views should share the same type if one
     *         can be converted to the other in {@link #getView}. Note: Integers must be in the
     *         range 0 to {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can
     *         also be returned.
     * @see #IGNORE_ITEM_VIEW_TYPE
     */
    int getItemViewType(int position);
    
    /**
     * <p>
     * Returns the number of types of Views that will be created by
     * {@link #getView}. Each type represents a set of views that can be
     * converted in {@link #getView}. If the adapter always returns the same
     * type of View for all items, this method should return 1.
     * </p>
     * <p>
     * This method will only be called when when the adapter is set on the
     * the {@link AdapterView}.
     * </p>
     * 
     * @return The number of types of Views that will be created by this adapter
     */
    int getViewTypeCount();
    
    static final int NO_SELECTION = Integer.MIN_VALUE;
 
     /**
      * @return true if this adapter doesn't contain any data.  This is used to determine
      * whether the empty view should be displayed.  A typical implementation will return
      * getCount() == 0 but since getCount() includes the headers and footers, specialized
      * adapters might want a different behavior.
      */
     boolean isEmpty();
}

        这里面只定义了方法,其中:

    void registerDataSetObserver(DataSetObserver observer);
    void unregisterDataSetObserver(DataSetObserver observer);

        这两个方法我们通常不需要太关心,因为ListView, GridView会自动去注册/取消注册的,DataSetObserver用的是一个观察者模式,目的就是当Adapter中的数据发生变化是,能够通知ListView 或 GridView。

    int getCount();   
    Object getItem(int position);
    long getItemId(int position);
    View getView(int position, View convertView, ViewGroup parent);

       当我们继承BaseAdapter时,通常需要实现以上四个方法,这四个方法大家应该用的很熟了,这里我就不说了;

    int getItemViewType(int position);
    int getViewTypeCount();

       特别要注意的是这两个方法,通常在BaseAdapter里面,默认是0和1,那我们在什么时候会需要override这两个方法呢?

       我们知道Adapter.getView中有个convertView,是用来复用View的,如果数据要进行分组,比如按照手机中的联系人按ABCD....来分类,那么,这个Listview就有两种view,一种是分组标签,另一种就是联系人,因此,我们需要设置ViewTypeCount为2,同时将分组view和联系人view设置不同的view type,这样,复用时,就能区分开来,有效的节省内存。

        2.2 ListAdapter

public interface ListAdapter extends Adapter {

    /**
     * Indicates whether all the items in this adapter are enabled. If the
     * value returned by this method changes over time, there is no guarantee
     * it will take effect.  If true, it means all items are selectable and
     * clickable (there is no separator.)
     * 
     * @return True if all items are enabled, false otherwise.
     * 
     * @see #isEnabled(int) 
     */
    public boolean areAllItemsEnabled();

    /**
     * Returns true if the item at the specified position is not a separator.
     * (A separator is a non-selectable, non-clickable item).
     * 
     * The result is unspecified if position is invalid. An {@link ArrayIndexOutOfBoundsException}
     * should be thrown in that case for fast failure.
     *
     * @param position Index of the item
     * 
     * @return True if the item is not a separator
     * 
     * @see #areAllItemsEnabled() 
     */
    boolean isEnabled(int position);
}

        这里面的方法就两个,一个是判断在Adapter中所有的item可选择或可点击,另一个是指点位置的item可选择或可点击,没啥可讲的;

        2.3 SpinnerAdapter

        这个是给下拉列表控件用的,就一个方法getDropDownView,其它没啥好讲的;

        2.4 BaseAdapter

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    public boolean hasStableIds() {
        return false;
    }
    
    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }
    
    /**
     * Notifies the attached observers that the underlying data has been changed
     * and any View reflecting the data set should refresh itself.
     */
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

    /**
     * Notifies the attached observers that the underlying data is no longer valid
     * or available. Once invoked this adapter is no longer valid and should
     * not report further data set changes.
     */
    public void notifyDataSetInvalidated() {
        mDataSetObservable.notifyInvalidated();
    }

    public boolean areAllItemsEnabled() {
        return true;
    }

    public boolean isEnabled(int position) {
        return true;
    }

    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        return getView(position, convertView, parent);
    }

    public int getItemViewType(int position) {
        return 0;
    }

    public int getViewTypeCount() {
        return 1;
    }
    
    public boolean isEmpty() {
        return getCount() == 0;
    }
}

        BaseAdapter也没啥讲的,我们继承时,只用实现它未实现的方法就行了,有需要的话,override一下也行。

三、RecycleBin

        在AbsListView(ListView / GridView)中,View复用全是依赖RecycleBin来缓存用过的View。

        在《Android ListView的理解(一)》中,讲解obtainView方法中,有提到过该类,不过,只说了如何用,而未说明这个类里面的方法和实现,这次就来分析下。

        3.1 类成员

    /**
     * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
     * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
     * start of a layout. By construction, they are displaying current information. At the end of
     * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
     * could potentially be used by the adapter to avoid allocating views unnecessarily.
     *
     * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
     * @see android.widget.AbsListView.RecyclerListener
     */
    class RecycleBin {
        private RecyclerListener mRecyclerListener;

        /**
         * The position of the first view stored in mActiveViews.
         */
        private int mFirstActivePosition;

        /**
         * Views that were on screen at the start of layout. This array is populated at the start of
         * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
         * Views in mActiveViews represent a contiguous range of Views, with position of the first
         * view store in mFirstActivePosition.
         */
        private View[] mActiveViews = new View[0];

        /**
         * Unsorted views that can be used by the adapter as a convert view.
         */
        private ArrayList<View>[] mScrapViews;
        private int mViewTypeCount;
        private ArrayList<View> mCurrentScrap;
    }

         区区几个成员变量:

        1. 当发生View回收时,mRecyclerListener若有注册,则会通知给注册者;

        2. mFirstActivePosition对应的是在ListView中,可视区域中的第一个item position(即getFirstVisiblePosition);

        3. mActiveViews存储着当前ListView中,可见区域中的View;

        4. mScrapViews是一个ArrayList,为啥?这个与mViewTypeCount相关,那么大家也就猜到了,mScrapViews缓存着ViewTypeCount种类型的View,默认是1,手机联系人是2等;

        5. mCurrentScrap就是指向当前mScrapViews中的一组,默认ViewTypeCount = 1的情况下,mCurrentScrap = mScrapViews[0];

        3.2 类方法分析

        3.2.1 setViewTypeCount

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

        设置ViewTypeCount,然后初始化类成员变量;

        3.2.2 markChildrenDirty

        public void markChildrenDirty() {
            if (mViewTypeCount == 1) {
                final ArrayList<View> 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<View> scrap = mScrapViews[i];
                    final int scrapCount = scrap.size();
                    for (int j = 0; j < scrapCount; j++) {
                        scrap.get(j).forceLayout();
                    }
                }
            }
        }

        将mScrapView中回收回来的View设置一样标志,在下次被复用到ListView中时,告诉viewroot重新layout该view

        forceLayout方法

    /**
     * Forces this view to be laid out during the next layout pass.
     * This method does not call requestLayout() or forceLayout()
     * on the parent.
     */
    public void forceLayout() {
        mPrivateFlags |= FORCE_LAYOUT;
        mPrivateFlags |= INVALIDATED;
    }

         该方法只是设置标志,并不会通知其parent来重新layout。

         3.2.3 shouldRecycleViewType

        public boolean shouldRecycleViewType(int viewType) {
            return viewType >= 0;
        }

         判断给定的view的viewType指明是否可以回收回。

         那什么情况下,viewType < 0?即不能回收?

         我们在ListView, GridView 或 AbsListView中,找不到小于0的TYPE定义,但是,在AbsListView的parent类AdapterView中找到了:

public abstract class AdapterView<T extends Adapter> extends ViewGroup {
    /**
     * The item view type returned by {@link Adapter#getItemViewType(int)} when
     * the adapter does not want the item's view recycled.
     */
    public static final int ITEM_VIEW_TYPE_IGNORE = -1;

    /**
     * The item view type returned by {@link Adapter#getItemViewType(int)} when
     * the item is a header or footer.
     */
    public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
	
	.........
}

        上述表明,指定忽略的,或者是 HeaderView / FootView是不被回收的。如有特殊需要可以将自己定义的viewType设置为-1,否则,将会浪费内存,导致OOM,切记!

        3.2.4 clear

        /**
         * Clears the scrap heap.
         */
        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);
                    }
                }
            }
        }

        这个方法一目了然,清理ScrapView中的View,并将这些View从窗口中Detach。

        3.2.5 fillActiveViews

        /**
         * 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) {
            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 != 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;
                }
            }
        }

        该方法就是填充mActiveView数组。当Adapter中的数据个数未发生变化时,此时用户可能只是滚动,或点击等操作,ListView中item的个数会发生变化,因此,需要将可视的item加入到mActiveView中来管理。

        3.2.6 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
         */
        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;
        }

        position是adapter中的绝对下标值,mFirstActivePosition前面说过了,是当前可视区域的下标值,对应在adapter中的绝对值,如果找到,则返回找到的View,并将mActiveView对应的位置设置为null。

        对于3.2.5和3.2.6这两个方法,在ListView.layoutChildren或GridView.layoutChildren中调用的,用户可能做了一些操作(未导致个数发生变化),以ListView.layoutChildren为例,代码片断:

    @Override
    protected void layoutChildren() {
            .........
			
            // Pull all children into the RecycleBin.
            // These views will be reused if possible
            final int firstPosition = mFirstPosition;
            final RecycleBin recycleBin = mRecycler;

            // reset the focus restoration
            View focusLayoutRestoreDirectChild = null;

            // Don't put header or footer views into the Recycler. Those are
            // already cached in mHeaderViews;
            if (dataChanged) {
                for (int i = 0; i < childCount; i++) {
                    recycleBin.addScrapView(getChildAt(i), firstPosition+i);
                    if (ViewDebug.TRACE_RECYCLER) {
                        ViewDebug.trace(getChildAt(i),
                                ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);
                    }
                }
            } else {
                recycleBin.fillActiveViews(childCount, firstPosition);
            }
			
            .........
			
            fillXXX
			
            .........
			
            // Flush any cached views that did not get reused above
            recycleBin.scrapActiveViews();
    }

        上面代码分成了三段:

        step1:如果数据发生变化,就将所有view加入到mScrapView中,否则,将所有view放到mActiveView中;

        step2:添加view到listview中;

        step3:回收mActiveView中的未使用的view到mScrapView中;

        注:在step1中,如果是addScrapView,则所有的view将会detach,如果是fillActiveViews,则不会detach,只有在step3中,未用到的view才会detach。

        3.2.7 retrieveFromScrap(这个不属于RecycleBin类,是属于AbslistView中的方法,不过为了讲之后的方法,这个方法就单独拿出来讲解)

    static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
        int size = scrapViews.size();
        if (size > 0) {
            // See if we still have a view for this position.
            for (int i=0; i<size; i++) {
                View view = scrapViews.get(i);
                if (((AbsListView.LayoutParams)view.getLayoutParams())
                        .scrappedFromPosition == position) {
                    scrapViews.remove(i);
                    return view;
                }
            }
            return scrapViews.remove(size - 1);
        } else {
            return null;
        }
    }

        根据position,从mScrapView中找:

        1. 如果有view.scrappedFromPosition = position的,直接返回该view;

        2. 否则返回mScrapView中最后一个;

        3. 如果缓存中没有view,则返回null;

        下面,我们来分析下这三种情况在什么条件下满足?

         a. 第三种情况,这个最简单:

         一开始,listview稳定后,显示N个,此时mScrapView中是没有缓存view的,当我们向上滚动一小段距离(第一个此时仍显示部分),新的view将会显示,此时listview会调用Adapter.getView,但是缓存中没有,因此convertView是null,所以,我们得分配一块内存来创建新的convertView;

         b. 第二种情况:

         在a中,我们继续向上滚动,直接第一个view完全移出屏幕(假设没有新的item),此时,第一个view就会被detach,并被加入到mScrapView中;然后,我们还继续向上滚动,直接后面又将要显示新的item view时,此时,系统会从mScrapView中找position对应的View,显然,是找不到的,则将从mScrapView中,取最后一个缓存的view传递给convertView;

        c. 第一种情况:

        紧接着在b中(标示为橙色的文字后面),第一个被完全移出,加入到mScrapView中,且没有新增的item到listview中,此时,缓存中就只有第一个view;然后,我此时向下滑动,则之前的第一个item,将被显示出来,此时,从缓存中查找position对应的view有没有,当然,肯定是找到了,就直接返回了。

        以上三种情况分析完毕!大家也理解了吧,没完全理解的,多想想就能想通啦。

        3.2.8 getScrapView

        /**
         * @return A view from the ScrapViews collection. These are unordered.
         */
        View getScrapView(int position) {
            if (mViewTypeCount == 1) {
                return retrieveFromScrap(mCurrentScrap, position);
            } else {
                int whichScrap = mAdapter.getItemViewType(position);
                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
                    return retrieveFromScrap(mScrapViews[whichScrap], position);
                }
            }
            return null;
        }

        getScrapView实际上就是根据viewType,来查找不同的mScrapView,分析了retrieveFromScrap后,getScrapView就很清楚了。

        3.2.9 addScrapView

        /**
         * Put a view into the ScapViews list. These views are unordered.
         *
         * @param scrap The view to add
         */
        void addScrapView(View scrap, int position) {
            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)) {
                if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                    removeDetachedView(scrap, false);
                }
                return;
            }

            lp.scrappedFromPosition = position;

            if (mViewTypeCount == 1) {
                scrap.dispatchStartTemporaryDetach();
                mCurrentScrap.add(scrap);
            } else {
                scrap.dispatchStartTemporaryDetach();
                mScrapViews[viewType].add(scrap);
            }

            if (mRecyclerListener != null) {
                mRecyclerListener.onMovedToScrapHeap(scrap);
            }
        }

        此方法顾名思义,就是将移出可视区域的view,设置它的scrappedFromPosition,然后从窗口中detach该view,并根据viewType加入到mScrapView中。

        3.2.10 scrapActiveViews

        /**
         * Move all views remaining in mActiveViews to mScrapViews.
         */
        void scrapActiveViews() {
            final View[] activeViews = mActiveViews;
            final boolean hasListener = mRecyclerListener != null;
            final boolean multipleScraps = mViewTypeCount > 1;

            ArrayList<View> scrapViews = mCurrentScrap;
            final int count = activeViews.length;
            for (int i = count - 1; i >= 0; i--) {
                final View victim = activeViews[i];
                if (victim != null) {
                    final AbsListView.LayoutParams lp
                            = (AbsListView.LayoutParams) victim.getLayoutParams();
                    int whichScrap = lp.viewType;

                    activeViews[i] = null;

                    if (!shouldRecycleViewType(whichScrap)) {
                        // Do not move views that should be ignored
                        if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
                            removeDetachedView(victim, false);
                        }
                        continue;
                    }

                    if (multipleScraps) {
                        scrapViews = mScrapViews[whichScrap];
                    }
                    victim.dispatchStartTemporaryDetach();
                    lp.scrappedFromPosition = mFirstActivePosition + i;
                    scrapViews.add(victim);

                    if (hasListener) {
                        mRecyclerListener.onMovedToScrapHeap(victim);
                    }

                    if (ViewDebug.TRACE_RECYCLER) {
                        ViewDebug.trace(victim,
                                ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP,
                                mFirstActivePosition + i, -1);
                    }
                }
            }

            pruneScrapViews();
        }

        在分析3.2.5和3.2.6方法时,step3已经提到这个方法了,实际上就是将mActiveView中未使用的view回收(因为,此时已经移出可视区域了)。

        3.2.11 pruneScrapViews

        /**
         * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
         * (This can happen if an adapter does not recycle its views).
         */
        private void pruneScrapViews() {
            final int maxViews = mActiveViews.length;
            final int viewTypeCount = mViewTypeCount;
            final ArrayList<View>[] scrapViews = mScrapViews;
            for (int i = 0; i < viewTypeCount; ++i) {
                final ArrayList<View> scrapPile = scrapViews[i];
                int size = scrapPile.size();
                final int extras = size - maxViews;
                size--;
                for (int j = 0; j < extras; j++) {
                    removeDetachedView(scrapPile.remove(size--), false);
                }
            }
        }

        该方法解释为:mScrapView中每个ScrapView数组大小不应该超过mActiveView的大小,如果超过,系统认为程序并没有复用convertView,而是每次都是创建一个新的view,为了避免产生大量的闲置内存且增加OOM的风险,系统会在每次回收后,去检查一下,将超过的部分释放掉,节约内存降低OOM风险。

        3.2.12 reclaimScrapViews

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

        将mScrapView中所有的缓存view全部添加到指定的view list中,只看到有AbsListView.reclaimViews有调用到,但没有其它方法使用这个函数,可能在特殊情况下会使用到,但目前从framework中,看不出来。

        3.2.13 setCacheColorHint

        /**
         * Updates the cache color hint of all known views.
         *
         * @param color The new cache color hint.
         */
        void setCacheColorHint(int color) {
            if (mViewTypeCount == 1) {
                final ArrayList<View> scrap = mCurrentScrap;
                final int scrapCount = scrap.size();
                for (int i = 0; i < scrapCount; i++) {
                    scrap.get(i).setDrawingCacheBackgroundColor(color);
                }
            } 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++) {
                        scrap.get(j).setDrawingCacheBackgroundColor(color);
                    }
                }
            }
            // Just in case this is called during a layout pass
            final View[] activeViews = mActiveViews;
            final int count = activeViews.length;
            for (int i = 0; i < count; ++i) {
                final View victim = activeViews[i];
                if (victim != null) {
                    victim.setDrawingCacheBackgroundColor(color);
                }
            }
        }

        该方法就是为所有的view绘置它们的背景色。

四、总结

        通过分析Adapter, BaseAdapter之间的关系,以及分析RecycleBin里面的方法,和对应使用的情况,我想,大家应该也很清楚了。RecycleBin是一个很重要的类,学习了这个类,无论是我们通过ListView 或 GridView来间接使用它,还是将来自己写控件时,需要考虑到复用view时,都很有帮助。

        ListView, GridView 以及 AbsListView 包括它们的parent都是很复杂的,我们不能一头埋进去,否则你会发现无从下手,我们只能通过一些关键的类,接口或方法,来找到突破口,理解了这些代码,再去看整个类时,你会发现,其实没那么神秘,甚至有些地方我们都可以联想到情景。

        好了,暂时就这么多,当然,对ListView的分析仍未结束,因为,我们只是了解了它的VIEW的添加,删除机制,但它如何与用户交互(即Touch,Scroll,Fling,Click, LongClick等)都未分析,大家可以尝试着去看看,我会在之后的时间里,慢慢的带领大家一起学习,分析。

       有什么不对的地方,或是值得大家分析,讨论的,也欢迎大家积极留言,谢谢!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值