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;
}
-
一级缓存 mAttachedScrap和mChangedScrap
前者用来缓存还在屏幕中的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。
流程图
上面的步骤大概可以总结为如下流程:
进行缓存
如果调用了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);
}
}