RecyclerView缓存机制小结

RecyclerView缓存机制小结

概述

RecyclerView对ListView有了一些优化,多了几种缓存,因此性能上有所提升,当然逻辑也更加复杂,但是再复杂它也就是一个 ”自定义“ View而已。

缓存分析

Recycler

RecyclerView的缓存主要靠Recycler这个内部类,其中的几个list集合用来缓存复用ViewHolder:

public final class Recycler {
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;

    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

    RecycledViewPool mRecyclerPool;

    private ViewCacheExtension mViewCacheExtension;

}
  • 一级缓存 mAttachedScrapmChangedScrap

    前者用来缓存还在屏幕中的ViewHolder,后者用来缓存数据已经改变的 ViewHolder

  • 二级缓存 mCachedViews

    用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。

  • 三级缓存 mViewCacheExtension:自定义缓存,一般不会使用

  • 四级缓存 mRecyclerPool

    mCachedViews 中如果存不下新的 ViewHolder 时,就会把 ViewHolder 存入RecyclerViewPool 中

layout过程

因为RecyclerView也是一种 “View”嘛,而且它和ListView类似,分析measure和draw过程意义不大,直接来看layout过程。

调用链

//有些方法省略了参数
RecyclerView.onLayout()->
RecyclerView.dispatchLayout()->	//如果没有设置Adapter和layoutmanager,这里就会直接返回,出现错误
RecyclerView.dispatchLayoutStep2()->		//实际layout的过程在这
mLayout.onLayoutChildren()->	//mLayout是RecyclerView的内部类LayoutManager,它的onLayoutChildren方法是空实现,交给了具体的子类实现
LinearLayoutManager.onLayoutChildren()->	//LinearLayoutManager的实现
LinearLayoutManager.fill()->	//作用是给RecyclerView填充布局
LinearLayoutManager.layoutChunk()->		//循环调用,每次调用填充一个ItemView
LinearLayoutManager.LayoutState.next()->
RecyclerView.Recycler.getViewForPosition(int)->		//又调用回了RecyclerView的方法
Recycler.getViewForPosition(int, boolean)->		//调用下面方法获取ViewHolder,并向上返回itemView 
Recycler.tryGetViewHolderForPositionByDeadline(int position,...)

tryGetViewHolderForPositionByDeadline(int position,…)

这个方法根据传入的position从各级缓存中依次寻找ViewHolder或调用Adapter的createViewHolder方法创建ViewHolder。

这个方法很重要,体现了RecyclerView的缓存策略。

//下面省略了很多无关代码
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                                                 boolean dryRun, long deadlineNs) {
    ...
    if (mState.isPreLayout()) {
        // 0 如果pre-layout,会从mChangedScrap去获取ViewHolder
        holder = getChangedScrapViewForPosition(position);
    }

    if (holder == null) {
        // 1 先后从 mAttachedScrap、 mHiddenViews、mCachedViews根据position获取 ViewHolder
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        	 if (holder != null) {
	     if (!validateViewHolderForOffsetPosition(holder)) {	//如果当前position的ViewHolder无效
	 			...
	             //从scrap中移除
	             if (holder.isScrap()) {
	                 removeDetachedView(holder.itemView, false);
	                 holder.unScrap();
	             } else if (holder.wasReturnedFromScrap()) {
	                 ...
	             }
	             //放到ViewCache或者Pool中
	             recycleViewHolderInternal(holder);
	         }
	         //置空继续寻找
	         holder = null;
	     } else {
	         fromScrapOrHiddenOrCache = true;
	     }
    }

    if (holder == null) {
        final int type = mAdapter.getItemViewType(offsetPosition);	//多子布局加载时会重新getItemViewType这个方法,根据position返回不同的itemview类型
        // 2 如果Adapter的hasStableIds方法返回true,就通过子view类型和Id从 mAttachedScrap和mCachedViews寻找缓存
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
            if (holder != null) {
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true;
            }
        }

        if (holder == null && mViewCacheExtension != null) {
            // 3 从自定义缓存获取
            View view = mViewCacheExtension
                getViewForPositionAndType(this, position, type);
            holder = getChildViewHolder(view);
        }
    }

    if (holder == null) {
        // 4 从mRecyclerPool获取ViewHolder
        holder = getRecycledViewPool().getRecycledView(type);
        if (holder != null) {
            holder.resetInternal();
            if (FORCE_INVALIDATE_DISPLAY_LIST) {
                invalidateDisplayListInt(holder);
            }
        }
    }

    if (holder == null) {
        // 5 缓存都获取不到,通过Adapter的createViewHolder()方法加载
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
    }
    ...
}

小结一下上面的缓存策略:

  • 先从前两级缓存 mChangedScrap、mAttachedScrap 和 mCachedViews 中获取ViewHolder
  • 从自定义缓存mViewCacheExtension获取ViewHolder
  • 再从mRecyclerPool获取ViewHolder
  • 如果所有缓存都获取不到,就通过Adapter的createViewHolder(parent: ViewGroup, viewType: Int)方法(一般我们会重写这个方法根据viewType参数加载不同的布局)获取inflate加载的itemview

获取缓存过程

也就是上面layout过程中使用各级缓存的过程。

第一次获取缓存

上面getScrapOrHiddenOrCachedHolderForPosition方法根据position先后从 mAttachedScrap、 mHiddenViews、mCachedViews 获取 ViewHolder

注意:第一次从mAttachedScrap、 mHiddenViews或mCachedViews获取缓存,获取后会验证能否在当前position使用(会判断对应position的ViewHolder的类型是否和缓存中取出的ViewHolder类型相同,不同则无法使用);如果可以使用,后面会直接进行复用,不会再调用Adapter.bindViewHolder()这个方法了

那么这是为什么呢?

因为这些缓存的ViewHolder只是发生了位置的改变,其他的数据等都没有发生改变,因此不需要再调用bindViewHolder让ViewHolder再和数据进行绑定了。

if (holder == null) {
    // 1 先后从 mAttachedScrap、 mHiddenViews、mCachedViews根据position获取 ViewHolder
    holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    if (holder != null) {
        if (!validateViewHolderForOffsetPosition(holder)) {	//这个方法会判断上面取出的缓存中的ViewHolder能否在当前position复用
            ...
                //不能复用,就从scrap中移除
                if (holder.isScrap()) {
                    removeDetachedView(holder.itemView, false);
                    holder.unScrap();
                } else if (holder.wasReturnedFromScrap()) {
                    holder.clearReturnedFromScrapFlag();
                }
            //放到mCachedViews(如果其中缓存已满会先移除一个再加入)或者mRecyclerPool中(超过最大缓存数(默认为5),就不会放入)
            recycleViewHolderInternal(holder);
        }
        //置空继续寻找
        holder = null;
    } else {
        fromScrapOrHiddenOrCache = true;
    }
}


ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
    final int scrapCount = mAttachedScrap.size();

    for (int i = 0; i < scrapCount; i++) {
        //先从mAttachedScrap获取
        final ViewHolder holder = mAttachedScrap.get(i);
        if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
            && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
            return holder;
        }
    }

    if (!dryRun) {
        View view = mChildHelper.findHiddenNonRemovedView(position);
        if (view != null) {
            //从mHiddenViews获取
            final ViewHolder vh = getChildViewHolderInt(view);
            mChildHelper.unhide(view);
            int layoutIndex = mChildHelper.indexOfChild(view);
            if (layoutIndex == RecyclerView.NO_POSITION) {
                throw new IllegalStateException("layout index should not be -1 after "
                                                + "unhiding a view:" + vh + exceptionLabel());
            }
            mChildHelper.detachViewFromParent(layoutIndex);
            scrapView(view);
            vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
                        | ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
            return vh;
        }
    }

    final int cacheSize = mCachedViews.size();
    for (int i = 0; i < cacheSize; i++) {
        //从mCachedViews获取
        final ViewHolder holder = mCachedViews.get(i);
        if (!holder.isInvalid() && holder.getLayoutPosition() == position
            && !holder.isAttachedToTransitionOverlay()) {
            if (!dryRun) {
                mCachedViews.remove(i);
            }
            return holder;
        }
    }
    return null;
}

第二次获取缓存

通过getScrapOrCachedViewForId方法根据从mAttachedScrap或mCachedViews获取:

ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
    final int count = mAttachedScrap.size();
    for (int i = count - 1; i >= 0; i--) {
        //先从mAttachedScrap获取
        final ViewHolder holder = mAttachedScrap.get(i);
        if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
            if (type == holder.getItemViewType()) {
                holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                if (holder.isRemoved()) {
                    if (!mState.isPreLayout()) {
                        holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
                                        | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
                    }
                }
                return holder;
            } else if (!dryRun) {
                //dryRun应该是判断itemview是否有动画,有动画就会从mAttachedScrap移除并加入mCacheView或者mRecyclerPool
                mAttachedScrap.remove(i);
                removeDetachedView(holder.itemView, false);
                quickRecycleScrapView(holder.itemView);
            }
        }
    }

    final int cacheSize = mCachedViews.size();
    for (int i = cacheSize - 1; i >= 0; i--) {
        //再从mCachedViews获取
        final ViewHolder holder = mCachedViews.get(i);
        if (holder.getItemId() == id && !holder.isAttachedToTransitionOverlay()) {
            if (type == holder.getItemViewType()) {
                if (!dryRun) {
                    mCachedViews.remove(i);
                }
                return holder;
            } else if (!dryRun) {
                recycleCachedViewAt(i);
                return null;
            }
        }
    }
    return null;
}

第三次获取缓存

从自定义缓存mViewCacheExtension获取

getViewForPositionAndType是个抽象方法,需要我们自己实现,但是我们一般不会使用这个自定义缓存。

第四次获取缓存

从mRecyclerPool中获取,RecycledViewPool的默认缓存数量为5。

这里获取的缓存会调用Adapter.bindViewHolder()重新和数据绑定

if (holder == null) {
    // 4 从mRecyclerPool获取ViewHolder
    holder = getRecycledViewPool().getRecycledView(type);
    if (holder != null) {
        holder.resetInternal();
        if (FORCE_INVALIDATE_DISPLAY_LIST) {
            invalidateDisplayListInt(holder);
        }
    }
}


//RecycledViewPool.getRecycledView()
public ViewHolder getRecycledView(int viewType) {
    final ScrapData scrapData = mScrap.get(viewType);    //mScrap是RecyclerPool中存储数据的对象,这是一个SparseArray
    if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
        final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
        for (int i = scrapHeap.size() - 1; i >= 0; i--) {
            if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
                return scrapHeap.remove(i);
            }
        }
    }
    return null;
}
SparseArray

SparseArray是RecycledViewPool的内部类,是Android中特有的数据结构,译作“稀疏数组”,但是它类似于HashMap。

其中用于存储的数据结构如下:

public class SparseArray<E> implements Cloneable { 
	private int[] mKeys;	//直接将key声明为int数组,避免自动拆装箱,较HashMap性能有所提升

    private Object[] mValues;
}

SparseArray相较于HashMap的优势

  • 直接将key声明为int数组,避免自动拆装箱;
  • 不使用额外的结构体(Entry),单个元素的存储成本下降
  • 被删除的元素不会被立即删除(value被置为DELETE,这是一个Object对象),而是后面会通过gc()这个方法压缩数组时将它们删除,以节省存储空间。

SparseArray缺点:

在增删元素时都需要使用二分查找来获取对应的下标(O(logn)),较HashMap(O(1))低,在数组元素很多的时候体现的尤为明显。

那么为什么RecycledViewPool要使用SparseArray来替代HashMap呢?

这篇文章分析的挺不错:Android为什么推荐使用SparseArray来替代HashMap?

这里我再来小结一下:

因为RecycledViewPool中存储的是itemview的类型到缓存的ViewHolder的映射,而我们的RecyclerView的子项的类型一般也不会太多,尽管SparseArray在增删元素的时候使用二分查找效率较HashMap低,但元素较少时查找效率也不会差距太大;同时SparseArray的优势上面也提到过,主要是相较于HashMap节省内存空间,所以这里使用了SparseArray。

流程图

上面的步骤大概可以总结为如下流程:

img

进行缓存

如果调用了Adapter的一系列notifyXXX方法,最终会进行缓存。

调用链

LinearLayoutManager.onLayoutChildren(...)-> LayoutManager.detachAndScrapAttachedViews(recycler)-> LayoutManager.scrapOrRecycleView(..., view)->
Recycler.scrapView(view);

下面看看最后两个方法:

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        //缓存到 mCacheViews 或 RecyclerViewPool
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        //缓存到mAttachedScrap 或 mChangedScrap
        recycler.scrapView(view); 
    }
}

void recycleViewHolderInternal(ViewHolder holder) {
    ...
    if (forceRecycle || holder.isRecyclable()) {
        //如果ViewHolder有下列任何一个标识,就会加入RecyclerViewPool
        if (mViewCacheMax > 0
            && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                                        | ViewHolder.FLAG_REMOVED
                                        | ViewHolder.FLAG_UPDATE
                                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
            // Retire oldest cached view 删除mCachedViews中最“老”的缓存
            int cachedViewSize = mCachedViews.size();
            if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                recycleCachedViewAt(0);
                cachedViewSize--;
            }

            int targetCacheIndex = cachedViewSize;
            if (ALLOW_THREAD_GAP_WORK
                && cachedViewSize > 0
                && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                int cacheIndex = cachedViewSize - 1;
                while (cacheIndex >= 0) {
                    int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                    if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                        break;
                    }
                    cacheIndex--;
                }
                targetCacheIndex = cacheIndex + 1;
            }
            mCachedViews.add(targetCacheIndex, holder);	//将holder加入mCachedViews
            cached = true;
        }
        if (!cached) {
            //没有加入mCachedViews的就加入mRecyclerPool,其中最主要的一行代码就是getRecycledViewPool().putRecycledView(holder);
            addViewHolderToRecycledViewPool(holder, true);
            recycled = true;
        }
    } else {
        ...
    }
}


void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
        || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        //满足条件就加入mAttachedScrap
        mAttachedScrap.add(holder);
    } else {
        //否则加入mChangedScrap
        mChangedScrap.add(holder);
    }
}

参考资料

RecyclerView——缓存回收机制剖析总结

RecyclerView 的缓存复用机制

Android为什么推荐使用SparseArray来替代HashMap?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值