本文站在巨人的肩膀上 自我感觉又进了一步而成。

基于翔神的大作基础之上写的一个为RecyclerView添加HeaderView FooterView 的另一种解决方案,

翔神链接文首镇楼:http://blog.csdn.net/lmj623565791/article/details/51854533

上次翔神发表这篇文章时,我就提了个问题:说headerView和FooterView都是强引用在Adapter中的,这样即使他所属的ViewHolder被回收复用(后实践发现,就算设置了HeaderView的ViewHolder不缓存,但是始终有一个HeaderView的ViewHolder在被强引用),但是View本身的实例还是在被强引用,内存空间也无法释放的。 这样做虽然速度没任何问题,(甚至还有提升,但是HeaderView过大内存空间就会吃紧了吧) 因为我司项目大多HeaderView又臭又长,所以我想了好久 改写了一下,换了种思路,给RecyclerView提供数据和布局,并且可以让开发者动态配置headerView在RecyclerViewPool里的缓存数,将UI的创建 和 数据的绑定分开来做,都交由Adapter维护。

先给大家看一下我司app的某个界面设计稿:这种页面在我们的APP里有10+个

是的你没看错,底部还是个不断加载更多的列表~,对于这种又臭又长的HeaderView,我一想到它在内存里不能释放,我就浑身难受。

墙裂建议大家先阅读翔神文章后 再立刻阅读此文,威力翻倍。这样对本文使用到的一些吊炸天的东西就不会陌生了,例如通用的CommonAdapter和ViewHolder。

敲黑板,如果只是伸手党,建议直接看 【2 使用方法】,并直接到文末下载链接里的工程,拷贝recyclerview包下的几个文件即可使用。

工程里已经参考解决,HeaderView适配GridLayoutManager 和StaggeredGridLayoutManager。

========================================================================

【1 引言】

众所周知,RecyclerView已经是主流,ListView已经成为过去式,而两者之间有些许的不同,其中比较重要的一点就是ListView自带addHeaderView,addFooterView方法,而RecyclerView并没有提供。So,我们开发者要自己想办法实现这个功能。

市面上大多为RecyclerView添加HeaderView的方案,都是在使用RecyclerView的类中(Activity Fragment)里构建一个View,并绑定好数据,然后通过XXXAdapter提供的addHeaderView方法,将这个View set进Adapter里。

Adapter内部使用ArrayList、或者翔神使用的是SparseArray存储这个View,并为HeaderView FooterView分配不同的itemViewType,然后Adapter在onCreateViewHolder和onBindViewHolder方法里,根据ViewType的不同来判断这是HeaderView 还是普通item。

这种方法目前为止我只发现一个弊端(也是本文改进的地方),就是这个HeaderView由于在Adapter里是被ArrayList、SparseArray强引用的,就算其所属的RecyclerView.ViewHolder在RecyclerViewPool的缓存池里 被设置缓存数量为0,被回收了(后来经过实测,发现HeaderViewHolder数量多后,始终有一个ViewHolder在被引用 没有被释放,其余的被成功释放),但是这个View会因为被ArrayList等强引用着,依然停留在内存中。所以该HeaderView并没有被回收。而想一想普通的item都只有数据和layoutId传递给Adapter,并没有View的实例。

一般情况下 这并没有任何问题,因为普通项目的HeaderView也不大,但是若HeaderView过于庞大,(就像我司的项目,动辄HeaderView就三+个屏幕长度,三屏之后才是普通的item),在这个页面已经往下滑了很多距离,浏览了很多内容,HeaderView早已不可见,此时按照RecyclerView的思路,这个庞大的HeaderView所属的VIewHolder应该已经进入了RecyclerViewPool的缓存池中,如果设置该种viewType的缓存数量为0,即不缓存,ok,那么RecyclerView做了它该做的事,不再缓存这个HeaderView寄身的VIewHolder了,在GC垃圾回收触发后,虽然该种type的ViewHolder被回收了,可惜上文提到,此时HeaderView被强引用住,被回收的只是其所属的那个ViewHolder,这个庞大的VIew所占的内存空间依然没有被释放。

其实我们仔细想一想,RecyclerView Adapter里是不保存View对象的,它保存的只是数据和layout,而我们也应该遵循此原则 为其添加HeaderView(FooterView)。

(题外话,和ListView相比,RecyclerView更是进一步的 将 UI的创建 和数据的绑定 分成了两步,(oncreateViewHolder,onBindViewHolder))

敲黑板,本文就参考翔神的装饰者模式,为RecyclerView 添加 HeaderView(FooterView),

并且将HeaderView的UI创建,和数据绑定强制分开,提供配置每种headerView的缓存数量的方法,令HeaderView实例在Adapter中不再被强引用,让HeaderView和普通的ItemView没有两样~。

先上预览动图:

第二张图是为了测试headerViewHolder是否真的被回收特意选用4个ImageView组成的HeaderView看效果。

========================================================================

【2 使用方法】

//HeaderView使用方法小窥: 以下为Rv添加两个HeaderView
mHeaderAdapter = new HeaderRecyclerAndFooterWrapperAdapter(mAdapter) {@Override
    protected void onBindHeaderHolder(ViewHolder holder, int headerPos, int layoutId, Object o) {switch (layoutId) {case R.layout.item_header_1:TestHeader1 header1 = (TestHeader1) o;
                holder.setText(R.id.tv, header1.getText());
                break;
            case R.layout.item_header_2:TestHeader2 header2 = (TestHeader2) o;
                holder.setText(R.id.tv1, header2.getTxt1());
                holder.setText(R.id.tv2, header2.getTxt2());
                break;
            default:break;
        }}
};
mHeaderAdapter.addHeaderView(R.layout.item_header_1,new TestHeader1("第一个HeaderView"));
mHeaderAdapter.addHeaderView(R.layout.item_header_2,new TestHeader2("第二个","HeaderView"));
mRv.setAdapter(mHeaderAdapter);

以上每个HeaderView在缓存池RecyclerViewPool的数量都是默认的5个,

使用如下方法:

mHeaderAdapter.addHeaderView(R.layout.item_header_4, new TestHeader4(pics),0);

将该种类型的headerView的缓存数量配置为0个。

粗略这么一看,我擦 什么辣鸡,比翔神那个真是差十万八千里,人家只要4行代码就加一个HeaderView,而且还不用实现父类Adapter的方法,你这还要switch case 看起来就一坨好麻烦的样子,走了走了。

客官留步留步,如果客官有这种想法,先冷静一下,里听我港。

这个写法猛地看起来是略复杂了一些,但是它强制的让我们将UI的创建和数据的绑定分开了,我们重写的onBindHeaderHolder()方法,就是数据的绑定过程, 试想一下,基本上每个带HeaderView的页面都有下拉刷新功能,如果你使用传统方法添加HeaderView,那么你必须要持有HeaderView的引用才能在数据刷新时改变头部数据,而且那些烦人的set方法一样是要写一遍,你可能需要将 写在Activity(Fragment)里的 创建HeaderView时的set数据方法抽成一个函数,再调用一遍。所以工作量是一点没减少的。

而且重要的是,使用这种方法,如果将缓存数量设置为0,HeaderView在移出屏幕后,触发GC事件时,是可以被回收滴。文末给实验证明。

所以我们这种做法,你的工作量也是一点没增加滴!反而还是方便滴!优雅滴!

(躲开丢过来的鸡蛋)废话不多说,用法已经看到,下面看我们是怎么实现的。 如果伸手党看到这里觉得已经够了,那么就可以去文末直接下载源码copy使用了,里面使用的几个类版权大多归翔神所有。

========================================================================

【三,实现】

直接贴出核心代码:

public abstract class HeaderRecyclerAndFooterWrapperAdapter2 extends RecyclerView.Adapter<RecyclerView.ViewHolder> {private class HeaderBean {private final int DEFAULT_HEADER_VIEW_CACHE_SIZE = 5;//默认是5 和RecyclerViewPool的默认值一样
        private int layoutId;//viewType当做layoutId
        private Object data;//该viewType(LayoutId)对应的数据
        private int cacheSize;//该种viewType的HeaderView 在RecyclerViewPool的缓存池内的缓存数量

        public HeaderBean(int layoutId, Object data, int cacheSize) {this.layoutId = layoutId;
            this.data = data;
            this.cacheSize = cacheSize;
        }public HeaderBean(int layoutId, Object data) {this.layoutId = layoutId;
            this.data = data;
            this.cacheSize = DEFAULT_HEADER_VIEW_CACHE_SIZE;
        }public int getLayoutId() {return layoutId;
        }public void setLayoutId(int layoutId) {this.layoutId = layoutId;
        }public Object getData() {return data;
        }public void setData(Object data) {this.data = data;
        }public int getCacheSize() {return cacheSize;
        }public void setCacheSize(int cacheSize) {this.cacheSize = cacheSize;
        }}
//按照add顺序存放HeaderView的bean,bean包括layoutId,数据Data,和缓存数量cacheSize。
// 在createViewHOlder里根据layoutId创建UI,在onbindViewHOlder里依据这个data渲染UI,
// 在onAttachedToRecyclerView 为每种layoutId(同时也是viewType)的headerView设置缓存数量
private ArrayList<HeaderBean> mHeaderDatas = new ArrayList<HeaderBean>();
@Override
public int getItemViewType(int position) {if (isHeaderViewPos(position)) {return mHeaderDatas.get(position).getLayoutId();
//HeaderView的layoutId就是viewType
    }    return super.getItemViewType(position - getHeaderViewCount());}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {if (mHeaderDatas != null && !mHeaderDatas.isEmpty()) {//不为空,说明有headerview
        for (HeaderBean HeaderBean : mHeaderDatas) {if (HeaderBean.getLayoutId() == viewType) {//匹配上了说明是headerView
                return ViewHolderHeader3.get(parent.getContext(), null, parent, viewType, -1);
            }}}
protected abstract void onBindHeaderHolder(ViewHolderHeader3 holder, int headerPos, int layoutId, Object o);//多回传一个layoutId出去,用于判断是第几个headerview

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {if (isHeaderViewPos(position)) {onBindHeaderHolder((ViewHolderHeader3) holder, position, mHeaderDatas.get(position).getLayoutId(), mHeaderDatas.get(position).getData());
        return;
/**
 * 添加HeaderView
 *
 * @param layoutId headerView 的LayoutId
 * @param data     headerView 的data(可能多种不同类型的header 只能用Object了)
 */
public void addHeaderView(int layoutId, Object data) {mHeaderDatas.add(new HeaderBean(layoutId, data));
}/**
 * 添加HeaderView
 *
 * @param layoutId  headerView 的LayoutId
 * @param data      headerView 的data(可能多种不同类型的header 只能用Object了)
 * @param cacheSize 该种headerView在缓存池中的缓存个数
 */
public void addHeaderView(int layoutId, Object data, int cacheSize) {mHeaderDatas.add(new HeaderBean(layoutId, data, cacheSize));
}

定义一个HeaderBean,存放HeaderView 的布局id,需要绑定的数据data,以及该种HeaderView在RecyclerViewPool缓存池中的缓存数量。

将layoutId作为viewType。

定义个ArrayList<HeaderBean>,按照add进来的顺序 存放HeaderBean。

首先需要重写的就是getItemViewType()方法,在这个方法里根据postion判断是否在headerView的范围内,如果是返回layoutId作为viewType。

然后重写onCreateViewHolder()方法,如果headerData不为空,说明有HeaderView,那么遍历headerData,比较当前要创建的这个ViewHolder的type和headerData里的type,如果一样,说明则是要创建一个HeaderViewHolder。

在onBindViewHolder()方法中,先根据postion判断是否是HeaderView,如果是,那么我们便根据postion从headerDatas里取出相应的layoutId和data,回调一个 abstract  的 onBindHeaderHolder()的方法,将这些参数都传入,交由子类去自由处理。 子类在这个方法里 完成数据的绑定即可。

addHeaderView()方法比较简单,就是new 一个HeaderBean 然后add进HeaderDatas里即可。

下面,重点来了,我们将在onAttachedToRecyclerView()方法里,设置headerView的在RecyclerViewPool里的缓存数量。

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {mInnerAdapter.onAttachedToRecyclerView(recyclerView);
    //设置HeaderView的ViewHolder的缓存数量
    if (null != mHeaderDatas && !mHeaderDatas.isEmpty()) {for (HeaderBean HeaderBean : mHeaderDatas) {recyclerView.getRecycledViewPool().setMaxRecycledViews(HeaderBean.getLayoutId(), HeaderBean.getCacheSize());
        }}

判断如果headerdatas不为空,则遍历其中HeaderBean,并调用 recyclerView.getRecycledViewPool()获取RecyclerViewPool对象,再调用它的 setMaxRecycledViews()方法,传入viewType 和相应viewType的缓存数量。

====================================================================================================

【4 RecyclerViewPool 源码浅析】

关于RecyclerViewPool,可能还有很多人不是很了解(我也是最近才开始了解),这里简单说下我的理解,后续要深入研究一下RecyclerView相关的知识。

RecyclerViewPool是一个为RecyclerView缓存ViewHolder的缓存池,它默认会为每种viewType缓存5个ViewHolder。

源码为证:

public static class RecycledViewPool {private SparseArray<ArrayList<ViewHolder>> mScrap =new SparseArray<ArrayList<ViewHolder>>();
    private SparseIntArray mMaxScrap = new SparseIntArray();
    private int mAttachCount = 0;

    private static final int DEFAULT_MAX_SCRAP = 5;

这里的DEFAULT_MAX_SCRAP 就是每种viewType的默认缓存数量,本文所定义的默认数量和它保持一致,为5.

mScrap 以viewType为key,value是一个ArrayList,里面存放的就是该种ViewType的ViewHolder啦。

mMaxScrap 也以viewType为key,value就是每种viewType的最大缓存数量。

mAttachCount 是用来计数的,每当RecyclerViewPool与一个adapter绑定、解绑,回调onAdapterChanged()方法,在其中便会+1 -1,当它为0时,就会清空这个RecyclerViewPool缓存池里的所有ViewHolder。这便是缓存池中ViewHolder被清空的时刻了。

源码如下:

public void clear() {mScrap.clear();
}
void attach(Adapter adapter) {mAttachCount++;
}void detach() {mAttachCount--;
}/**
 * Detaches the old adapter and attaches the new one.
 * <p>
 * RecycledViewPool will clear its cache if it has only one adapter attached and the new
 * adapter uses a different ViewHolder than the oldAdapter.
 *
 * @param oldAdapter The previous adapter instance. Will be detached.
 * @param newAdapter The new adapter instance. Will be attached.
 * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
 *                               ViewHolder and view types.
 */
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
        boolean compatibleWithPrevious) {if (oldAdapter != null) {detach();
    }if (!compatibleWithPrevious && mAttachCount == 0) {clear();
    }if (newAdapter != null) {attach(newAdapter);
    }
}

我们在onAttachedtoRecyclerVie()方法里调用的recyclerView.getRecycledViewPool() 方法的源码如下:很简单 就是通过mRecycler获取RecyclerViewPool对象。

/**
 * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null;
 * if no pool is set for this view a new one will be created. See
 * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information.
 *
 * @return The pool used to store recycled item views for reuse.
 * @see #setRecycledViewPool(RecycledViewPool)
 */
public RecycledViewPool getRecycledViewPool() {return mRecycler.getRecycledViewPool();
}

紧接着调用的 设置缓存数量的方法源码如下:

public void setMaxRecycledViews(int viewType, int max) {mMaxScrap.put(viewType, max);
    final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
    if (scrapHeap != null) {while (scrapHeap.size() > max) {scrapHeap.remove(scrapHeap.size() - 1);
        }}
}

这里除了将mMaxScrap里的value改变以外,还从mScrap里取出了该种ViewType的缓存队列list,并且判断size,如果超过最大值,会remove掉相应ViewHolder。

RecyclerViewPool类还有几个其他的方法:

获取缓存ViewHolder

public ViewHolder getRecycledView(int viewType) {final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
    if (scrapHeap != null && !scrapHeap.isEmpty()) {final int index = scrapHeap.size() - 1;
        final ViewHolder scrap = scrapHeap.get(index);
        scrapHeap.remove(index);
        return scrap;
    }return null;
}

该方法通过viewType获取缓存的ViewHolder,可以看出,是从缓存list的尾部逐个取出ViewHolder的。

存入ViewHolder

public void putRecycledView(ViewHolder scrap) {final int viewType = scrap.getItemViewType();
    final ArrayList scrapHeap = getScrapHeapForType(viewType);
    if (mMaxScrap.get(viewType) <= scrapHeap.size()) {return;
    }if (DEBUG && scrapHeap.contains(scrap)) {throw new IllegalArgumentException("this scrap item already exists");
    }scrap.resetInternal();
    scrapHeap.add(scrap);
}

存入时 ,通过viewType获取对应ViewType的最大缓存数量 以及 对应viewType的缓存list,如果list.size大于等于最大值,则不缓存。 所以我们设置为0永远>=0,即没有缓存。(废话) 如果缓存list未满,则将该ViewHolder add进去,并且调用

void resetInternal() {mFlags = 0;
    mPosition = NO_POSITION;
    mOldPosition = NO_POSITION;
    mItemId = NO_ID;
    mPreLayoutPosition = NO_POSITION;
    mIsRecyclableCount = 0;
    mShadowedHolder = null;
    mShadowingHolder = null;
    clearPayload();
    mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
}

为这个ViewHolder恢复一些初始值,可以看到,都是一些flag itemId,postion等。

RecyclerViewPool缓存池一共缓存的ViewHolder数量:是所有viewType的ViewHolder数量之和。

int size() {int count = 0;
    for (int i = 0; i < mScrap.size(); i ++) {ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i);
        if (viewHolders != null) {count += viewHolders.size();
        }}return count;
}

最后一个方法是根据viewType获取相应缓存list,很简单,有就取出,没有就new一个,new的时候,要将自己put进mScrap中,如果从mMaxScrap中没有找到该种viewType对应的缓存数量上限,那么就使用默认值。

如果我们已经调用过了setMaxRecycledViews()方法,设置过缓存上限,那么mMaxScrap就能找到该种viewType对应的index,所以就不会设置为默认值5.

private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {ArrayList<ViewHolder> scrap = mScrap.get(viewType);
    if (scrap == null) {scrap = new ArrayList<>();
        mScrap.put(viewType, scrap);
        if (mMaxScrap.indexOfKey(viewType) < 0) {mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
        }}return scrap;
}

至此,所有的RecyclerViewPool的源码都已经看完啦,这个类还是比较简单的。

========================================================================

【五,完整代码】

这份代码FooterView并没有用此方法实现,是“强引用VIew方法实现的”。

理由:

1 因为FooterView往往是一个LoadMore相关的提示控件,内存占用很有限。

2 LoadMore相关提示的控件 是需要强引用在Fragment Activity 或者相关类中,即使我在Adapter类里将其引用释放,这个View在内存的空间依然是无法被释放的。

3 两种实现方法都放上来,大家可以根据本文描述的方法,自行尝试将FooterView也改写,可以和我讨论,稍后我也会附加上我修改的版本。

/**
 * 介绍:一个给RecyclerView添加HeaderView FooterView的装饰Adapter类
 * 重点哦~ RecyclerView的HeaderView将可以被系统回收,不像老版的HeaderView是一个强引用在内存里
 * 作者:zhangxutong
 * 邮箱:zhangxutong@imcoming.com
 * 时间: 2016/8/2.
 */
public abstract class HeaderRecyclerAndFooterWrapperAdapter2 extends RecyclerView.Adapter<RecyclerView.ViewHolder> {private class HeaderBean {private final int DEFAULT_HEADER_VIEW_CACHE_SIZE = 5;//默认是5 和RecyclerViewPool的默认值一样
        private int layoutId;//viewType当做layoutId
        private Object data;//该viewType(LayoutId)对应的数据
        private int cacheSize;//该种viewType的HeaderView 在RecyclerViewPool的缓存池内的缓存数量

        public HeaderBean(int layoutId, Object data, int cacheSize) {this.layoutId = layoutId;
            this.data = data;
            this.cacheSize = cacheSize;
        }public HeaderBean(int layoutId, Object data) {this.layoutId = layoutId;
            this.data = data;
            this.cacheSize = DEFAULT_HEADER_VIEW_CACHE_SIZE;
        }public int getLayoutId() {return layoutId;
        }public void setLayoutId(int layoutId) {this.layoutId = layoutId;
        }public Object getData() {return data;
        }public void setData(Object data) {this.data = data;
        }public int getCacheSize() {return cacheSize;
        }public void setCacheSize(int cacheSize) {this.cacheSize = cacheSize;
        }}private static final int BASE_ITEM_TYPE_FOOTER = 2000000;//footerView的ViewType基准值

    //按照add顺序存放HeaderView的bean,bean包括layoutId,数据Data,和缓存数量cacheSize。
    // 在createViewHOlder里根据layoutId创建UI,在onbindViewHOlder里依据这个data渲染UI,
    // 在onAttachedToRecyclerView 为每种layoutId(同时也是viewType)的headerView设置缓存数量
    private ArrayList<HeaderBean> mHeaderDatas = new ArrayList<HeaderBean>();
    private SparseArrayCompat<View> mFooterViews = new SparseArrayCompat<>();//存放FooterViews,key是viewType

    protected RecyclerView.Adapter mInnerAdapter;//内部的的普通Adapter

    public HeaderRecyclerAndFooterWrapperAdapter2(RecyclerView.Adapter mInnerAdapter) {this.mInnerAdapter = mInnerAdapter;
    }public int getHeaderViewCount() {return mHeaderDatas.size();
    }public int getFooterViewCount() {return mFooterViews.size();
    }private int getInnerItemCount() {return mInnerAdapter != null ? mInnerAdapter.getItemCount() : 0;
    }/**
     * 传入position 判断是否是headerview
     *
     * @param position
     * @return
     */
    public boolean isHeaderViewPos(int position) {// 举例, 2 个头,pos 0 1,true, 2+ false
        return getHeaderViewCount() > position;
    }/**
     * 传入postion判断是否是footerview
     *
     * @param position
     * @return
     */
    public boolean isFooterViewPos(int position) {//举例, 2个头,2个inner,pos 0 1 2 3 ,false,4+true
        return position >= getHeaderViewCount() + getInnerItemCount();
    }/**
     * 添加HeaderView
     *
     * @param layoutId headerView 的LayoutId
     * @param data     headerView 的data(可能多种不同类型的header 只能用Object了)
     */
    public void addHeaderView(int layoutId, Object data) {//mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, v);

/*        SparseArrayCompat headerContainer = new SparseArrayCompat();
        headerContainer.put(layoutId, data);
        mHeaderDatas.put(mHeaderDatas.size() + BASE_ITEM_TYPE_HEADER, headerContainer);*/

        mHeaderDatas.add(new HeaderBean(layoutId, data));
    }/**
     * 添加HeaderView
     *
     * @param layoutId  headerView 的LayoutId
     * @param data      headerView 的data(可能多种不同类型的header 只能用Object了)
     * @param cacheSize 该种headerView在缓存池中的缓存个数
     */
    public void addHeaderView(int layoutId, Object data, int cacheSize) {//mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, v);

/*        SparseArrayCompat headerContainer = new SparseArrayCompat();
        headerContainer.put(layoutId, data);
        mHeaderDatas.put(mHeaderDatas.size() + BASE_ITEM_TYPE_HEADER, headerContainer);*/

        mHeaderDatas.add(new HeaderBean(layoutId, data, cacheSize));
    }/**
     * 设置某个位置的HeaderView
     *
     * @param headerPos 从0开始,如果pos过大 就是addHeaderview
     * @param layoutId
     * @param data
     */
    public void setHeaderView(int headerPos, int layoutId, Object data) {if (mHeaderDatas.size() > headerPos) {
/*            SparseArrayCompat headerContainer = new SparseArrayCompat();
            headerContainer.put(layoutId, data);
            mHeaderDatas.setValueAt(headerPos, headerContainer);*/
            mHeaderDatas.get(headerPos).setLayoutId(layoutId);
            mHeaderDatas.get(headerPos).setData(data);
        } else if (mHeaderDatas.size() == headerPos) {//调用addHeaderView
            addHeaderView(layoutId, data);
        } else {//
            addHeaderView(layoutId, data);
        }}/**
     * 设置某个位置的HeaderView
     *
     * @param headerPos 从0开始,如果pos过大 就是addHeaderview
     * @param layoutId
     * @param data
     * @param cacheSize 该种headerView在缓存池中的缓存个数
     */
    public void setHeaderView(int headerPos, int layoutId, Object data, int cacheSize) {if (mHeaderDatas.size() > headerPos) {
/*            SparseArrayCompat headerContainer = new SparseArrayCompat();
            headerContainer.put(layoutId, data);
            mHeaderDatas.setValueAt(headerPos, headerContainer);*/
            mHeaderDatas.get(headerPos).setLayoutId(layoutId);
            mHeaderDatas.get(headerPos).setData(data);
            mHeaderDatas.get(headerPos).setCacheSize(cacheSize);
        } else if (mHeaderDatas.size() == headerPos) {//调用addHeaderView
            addHeaderView(layoutId, data, cacheSize);
        } else {//
            addHeaderView(layoutId, data, cacheSize);
        }}/**
     * 添加FooterView
     *
     * @param v
     */
    public void addFooterView(View v) {mFooterViews.put(mFooterViews.size() + BASE_ITEM_TYPE_FOOTER, v);
    }/**
     * 清空HeaderView数据
     */
    public void clearHeaderView() {mHeaderDatas.clear();
    }public void clearFooterView() {mFooterViews.clear();
    }public void setFooterView(View v) {clearFooterView();
        addFooterView(v);
    }@Override
    public int getItemViewType(int position) {if (isHeaderViewPos(position)) {return mHeaderDatas.get(position).getLayoutId();//HeaderView的layoutId就是viewType
        } else if (isFooterViewPos(position)) {//举例:header 2, innter 2, 0123都不是,4才是,4-2-2 = 0,ok。
            return mFooterViews.keyAt(position - getHeaderViewCount() - getInnerItemCount());
        }return super.getItemViewType(position - getHeaderViewCount());
    }@Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {if (mHeaderDatas != null && !mHeaderDatas.isEmpty()) {//不为空,说明有headerview
            for (HeaderBean HeaderBean : mHeaderDatas) {if (HeaderBean.getLayoutId() == viewType) {//匹配上了说明是headerView
                    return ViewHolderHeader3.get(parent.getContext(), null, parent, viewType, -1);
                }}}if (mFooterViews.get(viewType) != null) {//不为空,说明是footerview
            return new ViewHolder(parent.getContext(), mFooterViews.get(viewType));
        }return mInnerAdapter.onCreateViewHolder(parent, viewType);
    }//protected abstract RecyclerView.ViewHolder createHeader(ViewGroup parent, int headerPos);

    protected abstract void onBindHeaderHolder(ViewHolderHeader3 holder, int headerPos, int layoutId, Object o);//多回传一个layoutId出去,用于判断是第几个headerview

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {if (isHeaderViewPos(position)) {onBindHeaderHolder((ViewHolderHeader3) holder, position, mHeaderDatas.get(position).getLayoutId(), mHeaderDatas.get(position).getData());
            return;
        } else if (isFooterViewPos(position)) {return;
        }//举例子,2个header,0 1是头,2是开始,2-2 = 0
        mInnerAdapter.onBindViewHolder(holder, position - getHeaderViewCount());
    }@Override
    public int getItemCount() {return getInnerItemCount() + getHeaderViewCount() + getFooterViewCount();
    }@Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {mInnerAdapter.onAttachedToRecyclerView(recyclerView);
        //设置HeaderView的ViewHolder的缓存数量
        if (null != mHeaderDatas && !mHeaderDatas.isEmpty()) {for (HeaderBean HeaderBean : mHeaderDatas) {recyclerView.getRecycledViewPool().setMaxRecycledViews(HeaderBean.getLayoutId(), HeaderBean.getCacheSize());
            }}//为了兼容GridLayout
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();

            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {@Override
                public int getSpanSize(int position) {int viewType = getItemViewType(position);
                    if (isHeaderViewPos(position)) {return gridLayoutManager.getSpanCount();
                    } else if (mFooterViews.get(viewType) != null) {return gridLayoutManager.getSpanCount();
                    }if (spanSizeLookup != null)return spanSizeLookup.getSpanSize(position);
                    return 1;
                }});
            gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
        }}@Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {mInnerAdapter.onViewAttachedToWindow(holder);
        int position = holder.getLayoutPosition();
        if (isHeaderViewPos(position) || isFooterViewPos(position)) {ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();

            if (lp != null
                    && lp instanceof StaggeredGridLayoutManager.LayoutParams) {StaggeredGridLayoutManager.LayoutParams p =(StaggeredGridLayoutManager.LayoutParams) lp;

                p.setFullSpan(true);
            }}}
}

========================================================================

【6 实验】

是否使用了我这种方法,设置了headerView的缓存数量为0后,该HeaderView不在屏幕显示时,触发GC,内存空间真的能被回收呢,?那么就实验见真相。

这里我们借助 Android Studio 的Android Monitor里的dump java heap,它可以打印出当前内存里的对象的情况,还有旁边的小卡车按钮 Initiate GC,可以触发GC事件。

我的猜想,设置HeaderView的缓存数量为0,当向下滑动一段距离,HeaderView已不可见时,打印内存对象情况,HeaderViewHolder应该是一个“”野对象“”,没有任何引用,触发GC后,它将被回收,内存降下降。

而使用传统方法添加的HeaderView,即使设置缓存数量为0,由于View被强引用,内存空间也无法释放。

(剧透一点,我原本想的是HeaderViewHolder应该被释放,而HeaderView不会被释放,但实际上内存空间真的没有减少,但是HeaderViewHolder也被强引用住了,如有知情者 望不吝赐教 )

使用本方法:添加6个HeaderView,5个不设置缓存数量,1个设置缓存数量(加载四张图片的View),滑动到屏幕低端,Dump Java Heap,
可以看到有一个HeaderView的ViewHolder的Depth为空,说明没设置缓存数量的ViewHolder的确会被回收掉,这时候我们点击黄色小卡车 强制GC,
内存降了一半,这里面就有被回收掉的ViewHolder的功劳,ViewHolder被顺利回收,它里面引用的图片也可以被系统回收 腾出空间。
此时再Dump Java Heap 查看一下,果然被回收掉了一个ViewHolder对象。
再实验一下,只添加一个HeaderView(内含4个ImageView),然后滑动出屏幕后,HeaderViewHolder的数量,以及GC后的情况。
对应的图片如下:
此时就一个ViewHolderHeader的野对象,
GC后,它顺利被回收,内存下降(四张图片被回收的功效。)
这时,打开使用传统方式Adapter的Activity,也已经将这个RecyclerView的HeaderView的缓存设置为0
和上次操作一样,进入后,滑动界面到HeaderView移除屏幕,Dump Java Heap,
发现ViewHolderHeader3 的Depth是9,说明其正在被引用着,是无法被释放的,
这时我们就算再怎么点击Initiate GC,内存也不会下降很明显,GC后再查看Java Heap,ViewHolderHeader3的对象依然存在,说明其确实被强引用着没有被系统回收。

这里有个疑问,想问问各位,@翔神,在我的理解里,HeaderView实例由于在Adapter中强引用不会被回收,可是HeaderViewHolder 并没有被强引用住啊。这里看它确实被强引用了。这是一个疑问点,

其实本文到这里,可以结束了,我已经证明了本文的HeaderAdapter 确实可以将其ViewHolder回收,如果此时触发GC,将释放该部分内存空间,可是那个HeaderViewHolder的强引用的问题 我依然没有答案。我又做了一些实验,

不停的上下滑动,令这个HeaderView 一会滑出屏幕 一会进入屏幕,我的猜想中,应该会create N个 HeaderViewHolder。

查看java heap,果然如下图:

可以看到由于没有缓存,我们new了 9个ViewHolder出来,但是其中8个是野对象,可以被GC回收,此时屏幕上HeaderView已经不可见了,不明白为何还会有一个强引用。

此问题我将继续研究下去。。

========================================================================

【7 总结:】

以本文的方法添加的HeaderView以layoutId做itemType。

可以添加多种HeaderView。

可以设置每种HeaderView的ViewHolder在RecyclerViewPool缓存池中的数量。
设置为0则不缓存,当HeaderView不在屏幕可见时,触发GC的话, 它的ViewHolder将被回收,因为ViewHolder里的ItemView(HeaderView)本身也没有被其他强引用,所以它的内存空间将被释放。

就算不设置cacheSize,默认缺省值为5(和RecyclerViewPool的缺省值一致)。

同一种类型(相同的layoutId,viewType)的HeaderView也将被RecyclerVIew管理,不像之前那种方法 永远存于内存中。

例如,有两种item1类型的HeaderView,设置缓存数量为1,那么 RecyclerViewPool里会缓存 一份item1类型的ViewHolder,有一份View的空间可以被系统回收。

缓存本身是一种用空间换时间的技术,我们这么做,将更加灵活,headerView本身简单时,可以用默认配置,被RecyclerViewPool缓存住无法释放内存也无伤大雅,如果HeaderView本身很臃肿,占内存,滑出页面后,希望内存空间可以被回收,那么可以配置缓存数量,为0 则不缓存,但与此同时,我们获取了空间,就要付出时间的代价,每次滚回屏幕时,它的ViewHolder已经不存在,所以会重走onCreateViewHolder方法,这需要一定的时间。
所以说,这种方法只是让开发者多一种选择,万一出现特殊情况,想要释放HeaderView,也有计可施,实际开发中,大部分情况不需要设置缓存个数,

如果考虑多个RecyclerView共用同一个RecyclerViewPool,可为HeaderView设置1-2个缓存数。

RecyclerView涉及的东西还很多,多个RecyclerView共用一个RecyclerViewPool 我稍后将研究一下,争取也研究出一点小心得分享给大家。即使没有什么浏览量,多一个人看到也是好事。

说实话,设置缓存数量,95%的情况下用不到,但是假设一个场景,(我司app就如此),首页5个Tab,每个页面HeaderView长度2-3个屏幕,上面各种图片,5个Framgent切换是用hide show做的,所以5个页面全开时,将全部存于内存中,更可怕的是,每个页面头部再分2-3个子tab,每个子tab点开又是一坨。。。每个页面的风格都像h5。

而不用replace是希望用户每次切换tab回来后还能停留在上次浏览的地方,这就需要我们做一个抉择,很多时候还要和产品商量(不过他们既然设计出这种界面,我已经放弃和他们商量)。

如果依然要流畅的速度,还要这种又臭又长的页面,还不想内存OOMcrash,那么我只能选择!狗带~

========================================================================

本文工具类已经被收入该库:

https://github.com/mcxtzhang/all-base-adapter

该库还包括了N多的好用的Adapter。

========================================================================

源码链接:http://download.csdn.net/detail/zxt0601/9609911

========================================================================

【Android】让HeaderView也参与回收机制,自我感觉是优雅的为 RecyclerView 添加 HeaderView (FooterView)的解决方案相关推荐

  1. Android 优雅的为RecyclerView添加HeaderView和FooterView

    1.概述 RecyclerView通过其高度的可定制性深受大家的青睐,也有非常多的使用者开始对它进行封装或者改造,从而满足越来越多的需求. 如果你对RecyclerView不陌生的话,你一定遇到过这样 ...

  2. Android 泽宇GC垃圾回收机制算法

    GC是什么? GC的英文全称是:Gabage Collection,翻译成中文就是垃圾回收的意思.在一个对象不再被程序引用时,它所占用的堆空间就可以回收,以便于分配给新的对象使用. 那我们在了解回收机 ...

  3. Android 操作系统的进程回收机制

    Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使得其进程调度与资源管理 ...

  4. android 内存回收机制

    Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化, 使得其进程调度与资源管 ...

  5. Android 操作系统的内存回收机制。

    转载自品略网:http://www.pinlue.com/article/2020/03/0808/089994336918.html Android APP 的运行环境 Android 是一款基于 ...

  6. Flutter开发之ListView添加HeaderView和FooterView-2(39)

    参考文章:RecyclerView添加HeaderView和FooterView 接着Flutter开发之ListView添加HeaderView和FooterView-1 继续研究. 通过Recyc ...

  7. Android工程师进阶第一课 夯实Java基础 JVM内存模型和GC回收机制

    开篇词:跳出舒适区,走在Android行业的前端 你好,我是姜新星,一个深耕 Andorid 领域的老工程师. 记得 2010 年毕业典礼上,某位老师说"你们是最幸福的一届毕业生,正好赶上中 ...

  8. JAVA中的垃圾回收机制以及其在android开发中的作用

    http://blog.csdn.net/xieqibao/article/details/6707519 这篇文章概述了JAVA中运行时数据的结构,以及垃圾回收机制的作用.在后半部分,描述了如何检测 ...

  9. 安卓 java内存碎片_理解Android Java垃圾回收机制

    Jvm(Java虚拟机)内存模型 从Jvm内存模型中入手对于理解GC会有很大的帮助,不过这里只需要了解一个大概,说多了反而混淆视线. Jvm(Java虚拟机)主要管理两种类型内存:堆和非堆. 堆是运行 ...

最新文章

  1. 为什么说 SQL 是最成功的第四代语言?
  2. 用Javascript模拟微信飞机大战游戏
  3. [BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)
  4. java中的深度克隆浅克隆_了解Java中的可克隆接口
  5. php微博发布时间,PHP格式化显示时间函数,用于微博、社交媒体等,类似豆瓣
  6. Ubuntu下安装sublime text3并汉化
  7. VIM复制粘贴大全!
  8. Linux命令学习符以及安装程序
  9. ARM架构及ARM指令集、Thumb指令集你了解多少?
  10. MTK各个分区功能大全
  11. Windows下MySQL定时备份脚本
  12. IIS站点无法启动,万维网发布服务无法开机启动解决办法
  13. 三菱伺服电机马达使用注意事项
  14. 360安全软件设置白名单
  15. netts之 CTWSocket代码流程分析(整体是客户端请求式的)
  16. 微信web H5 安卓无法播放的解决方案
  17. 大系统观:1.4时间之矢
  18. 芬兰ZYFRA公司在首届中俄创新大赛中获胜
  19. Python3改变cmd(命令行)输出颜色
  20. 5G边缘计算网关助力山体滑坡监测系统

热门文章

  1. linux加解密指令,[Centos8] linux 上的Base64加解密指令
  2. 001-使用docker desktop for mac
  3. linux系统中怎样抓logo,linux启动成功修改logo
  4. 内存优化总结:ptmalloc、tcmalloc和jemalloc
  5. 黑客教父龚蔚:是谁打开了潘多拉的魔盒
  6. 22春天津大学《建筑施工》在线作业一
  7. ajaxSubmitDemo
  8. 赖世雄老师---主语
  9. SDHK_Tool.Static.SS_EulerAngleConversion 欧拉角,角度转换计算
  10. 看着窗户外行走的路人,始终看不见你