前一篇的ListView只是之前的学习的回顾和复习,RecyclerView才是目前学习的主要内容。这部分内容也比较多,因此详细分两部分来总结一下。而且对比之前的ListView,应该能更好理解RecyclerView的内容。
首先认识一下RecyclerView。这个东西有点杂乱。我们知道ListView可以用来显示很多的重复数据,按照item排列就行了。而RecyclerView综合了ListView和GridView的优点,它的职责也是将Datas中的数据以一定的规则展示在它的上面,说白了还是一个ViewGroup。那么它的功能怎么实现呢?
首先用一个adapter来和datas进行交流,adapter把datas变成ViewHolder的数据内容,这样RecyclerView就可以和数据进行交互了。这也是ListView的逻辑方式。但是RecyclerView同时还使用了一个LayoutManager,这样他就可以像GridView一样随意的变更布局格式了。然后他又有了自己的Recycler用于缓存管理view里面的内容。另外他还有getItemOffsets方法,ItemAnimator等等协助,于是有了动画,刷新,等等不同方面的支持。这些优点让它打败了ListView成为了主流的复用控件。
之前在ListView可以直到,ListView的复用就是从第n个item重新使用第一个item的view加载数据这种,无限循环使用,减少创建view所消耗的巨大开销。而RecyclerView在这一块也是这种原理,循环使用view。在缓存这个层面上,RecyclerView实际上并没有做出太大的创新,最大的创新来源于给每一个ViewHolder增加了一个UpdateOp,通过这个标志可以进行定向刷新指定的Item,并且通过Payload参数可以对Item进行局部刷新,提高了刷新时候的性能。这里也就不多赘述RecyclerView的绘制,直接进入核心的复用过程。
首先还是先放出Recycler类里面的几个关键的,也就是传说中的四级缓存
static final int DEFAULT_CACHE_SIZE = 2;//默认缓存的数量
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;//设置的缓存最大数量,默认为2
int mViewCacheMax = DEFAULT_CACHE_SIZE;//View的缓存的最大数量,默认为2
RecycledViewPool mRecyclerPool;//RecycledView,用来公用RecyclerView
//缓存的扩展,可以用来对指定的position跟Type进行缓存
private ViewCacheExtension mViewCacheExtension;
ArrayList mChangedScrap = null;//数据源更改过的AttachedScrap
final ArrayList mAttachedScrap = new ArrayList<>();//依附的缓存View
//缓存的全部View,包含可见跟不可见的ViewHolder
final ArrayList mCachedViews = new ArrayList();
private final List mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);//mAttachedScrap的不可变集合
mAttachedScrap: 用于缓存显示在屏幕上的 item 的 ViewHolder,RecyclerView 在 onLayout 时会先把 children 都移除掉,再重新添加进去,这个 List 是在布局中临时存放 children 的。和复用其实关系不大。
mViewCacheExtension: 留给开发者扩展
mCachedViews:这个集合里存的 ViewHolder 数据可以直接添加到 RecyclerView 中显示,不需要重新onBindViewHolder()。只有原来的item可以重新复用这个 ViewHolder。这里面存储两个ViewHolder
mRecyclerPool:用来缓存RecyclerView的集合。缓存了viewholder
在LayoutManager里面有这几个关键方法,在最后的next方法中跳回了recycler类,
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
//开始布局
fill(recycler, mLayoutState, state, false);
}
1.fill(recycler, mLayoutState, state, false); //填充itemview
2.layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result)
3.View next(RecyclerView.Recycler recycler)
跟进next方法,进入到recycler.getViewForPosition()方法,继续跟进到下面的代码
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
...
...
}
在这里判断isPreLayout() 时,就去 mChangedScrap 中找。而这个isPreLayout() 在 LayoutManager 的 onLayoutChildren 前就会置为false,因此跳过了这一段,往后走
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
//当前位置的viewholder是不是当前position的,不是就将viewholder置null
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
//
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
...
}
在上面判断完之后走到了这里,也就是getScrapOrHiddenOrCachedHolderForPosition()方法
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position,boolean dryRun){
final int scrapCount= mAttachScrap.size();
//Try first for an exact, non-invalid match for scarp.
for( int i = 0; i < scarpCount ; i++){
final ViewHolder holder = mAttachScrap.get(i);
if(!holder.wasReturnedFromScrap() && holder.getLayoutPosition() ==
position && !holder.isInvalid() && (mState.mInPreLayout ||
!holder.isRemoved())){
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
首先,去 mAttachedScrap 中寻找 position 一致的 viewHolder,需要确定这个 viewHolder 没有被移除,是否有效等条件,满足就返回这个 viewHolder。在RecyclerView重新layout的时候,比如Resume,会将所有chldren的holder移除掉,放到mAttachedScrap 中。在getScrapOrHiddenOrCachedHolderForPosition()方法中后面还会到hidden和mCached里去找holder。hidden没有比较清晰的描述,但是mCached就是之前说的二级缓存了。 遍历mCachedView,找到 position 一致的 ViewHolder,mCachedViews 里存放的 ViewHolder 的数据信息都保存着,只有原来的item可以重新复用这个 ViewHolder,新item无法从 mCachedViews 里拿 ViewHolder出来用。没有找到的话,就继续再找一遍,刚才是通过 position 来找,那这次就换成id,然后重复上面的步骤再找一遍,就进入了getScrapOrCachedViewForId()方法。
// Search in our first-level recycled view cache.
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
// invalid view holders may be in cache if adapter has stable ids as they can be
// retrieved via getScrapOrCachedViewForId
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
if (!dryRun) {
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
return holder;
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
//依然是到ScrapView和CachedView中找,通过id
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
ViewPool 会根据不同的 viewType 创建不同的集合来存放 ViewHolder,那么复用的时候,只要 ViewPool 里相同的 type 有 ViewHolder 缓存的话,就将最后一个拿出来复用,不用像 mCachedViews 需要各种匹配条件,只要有就可以复用。拿到 ViewHolder 之后,还会再次调用一个方法叫 resetInternal() 来重置 ViewHolder,这样 ViewHolder 就可以当作一个全新的 ViewHolder 来使用了,因此从这里拿的 ViewHolder 都需要重新 onBindViewHolder() 了。
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
这样recyclerview的四级缓存已经找过一遍了,这时候如果还是没找到就只能新创建Holder。调用 Adapter 的 onCreateViewHolder 来创建一个新的 ViewHolder 使用。tryGetViewHolderForPositionByDeadline() 这个方法基本就结束了。这大概就是 RecyclerView 的复用机制。
在 RecyclerView 滑动时,会交由 LinearLayoutManager 的 scrollVerticallyBy() 去处理,然后 LayoutManager 会接着调用 fill() 方法去处理需要复用和回收的卡位,最终会调用 recyclerView() 方法开始进行回收工作。回收的itemView优先存放到mCachedViews,如果mCachedViews已经满了,就移除一个出去到RecycledViewPool中去。
回收复用涉及到的结构体两个:
mCachedViews 和 RecyclerViewPool
mCachedViews 优先级高于 RecyclerViewPool,回收时,最新的 ViewHolder 都是往 mCachedViews 里放,如果它满了,那就移出一个扔到 ViewPool 里好空出位置来缓存最新的 ViewHolder。
复用时,也是先到 mCachedViews 里找 ViewHolder,但需要各种匹配条件,概括一下就是只有原来位置的卡位可以复用存在 mCachedViews 里的 ViewHolder,如果 mCachedViews 里没有,那么才去 ViewPool 里找。
在 ViewPool 里的 ViewHolder 都是跟全新的 ViewHolder 一样,只要 type 一样,有找到,就可以拿出来复用,重新绑定下数据即可。