分析RV的缓存机制,先思考一下什么时候用到缓存机制,从源码的哪个角度去看,缓存肯定是在滑动的时候去做的,所以我们从onTouchEvent()
的Action_Move开始看
public boolean onTouchEvent(MotionEvent e) {
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id "
+ mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false;
}
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
if (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
if (dx > 0) {
dx -= mTouchSlop;
} else {
dx += mTouchSlop;
}
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
} break;
}
滑动的监听里,调用了scrollByInternal()
内部封装的一个滑动方法:
boolean scrollByInternal(int x, int y, MotionEvent ev) {
int unconsumedX = 0, unconsumedY = 0;
int consumedX = 0, consumedY = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
eatRequestLayout();
onEnterLayoutOrScroll();
Trace.beginSection(TRACE_SCROLL_TAG);
if (x != 0) {
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
unconsumedX = x - consumedX;
}
if (y != 0) {
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
Trace.endSection();
repositionShadowingViews();
onExitLayoutOrScroll();
resumeRequestLayout(false);
}
if (!mItemDecorations.isEmpty()) {
invalidate();
}
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) {
// Update the last touch co-ords, taking any scroll offset into account
mLastTouchX -= mScrollOffset[0];
mLastTouchY -= mScrollOffset[1];
if (ev != null) {
ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
}
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
} else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
if (ev != null) {
pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
}
considerReleasingGlowsOnScroll(x, y);
}
if (consumedX != 0 || consumedY != 0) {
dispatchOnScrolled(consumedX, consumedY);
}
if (!awakenScrollBars()) {
invalidate();
}
return consumedX != 0 || consumedY != 0;
}
在内部滑动方法中,调用了mLayout.scrollHorizontallyBy()/mLayout.scrollVerticallyBy()
;
@VisibleForTesting LayoutManager mLayout;
这是RV的一个成员变量,使用setLayoutManager设置,RV将他的布局的操作都封装到了LayoutManager中,常见的有LinearLayoutManager,GridLayoutManager;
LinearLayoutManager的实现
/**
* {@inheritDoc}
*/
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == VERTICAL) {
return 0;
}
return scrollBy(dx, recycler, state);
}
/**
* {@inheritDoc}
*/
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || dy == 0) {
return 0;
}
mLayoutState.mRecycle = true;
ensureLayoutState();
final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDy = Math.abs(dy);
updateLayoutState(layoutDirection, absDy, true, state);
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
if (DEBUG) {
Log.d(TAG, "Don't have any more elements to scroll");
}
return 0;
}
final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
mOrientationHelper.offsetChildren(-scrolled);
if (DEBUG) {
Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
}
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}
fill()
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {
...
// 回收
recycleByLayoutState(recycler, layoutState);
// 复用
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
fill()中有两个核心方法,回收和复用;
回收机制
recycleViewHolderInternal()
void recycleViewHolderInternal(ViewHolder holder) {
...
recycleCachedViewAt(0);
...
}
void recycleCachedViewAt(int cachedViewIndex) {
...
// 取出需要回收的VH
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
// 放到回收池中
addViewHolderToRecycledViewPool(viewHolder, true);
// 删除这个VH
mCachedViews.remove(cachedViewIndex);
}
addViewHolderToRecycledViewPool()
void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
...
// 触发Adapter和RV层设置的回调
dispatchViewRecycled(holder);
// 放入回收池
getRecycledViewPool().putRecycledView(holder);
...
}
void dispatchViewRecycled(ViewHolder holder) {
if (mRecyclerListener != null) {
mRecyclerListener.onViewRecycled(holder);
}
if (mAdapter != null) {
mAdapter.onViewRecycled(holder);
}
if (mState != null) {
mViewInfoStore.removeViewHolder(holder);
}
}
// 放入回收池
public void putRecycledView(ViewHolder scrap) {
// 获取viewType
final int viewType = scrap.getItemViewType();
// 根据viewType取出SparseArray中的list
final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
scrap.resetInternal();
// 添加到list中
scrapHeap.add(scrap);
}
复用机制
Recycler类
public final class Recycler {
final ArrayList mAttachedScrap = new ArrayList<>();
ArrayList mChangedScrap = null;
final ArrayList mCachedViews = new ArrayList();
private final List
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
}
Recycler类是RV复用机制的核心实现类,设计了四级缓存,优先级顺序是:Scrap、CacheView、ViewCacheExtension、RecycledViewPool
-
mAttachedScrap:
不参与滑动时的回收复用,只保存重新布局时从RecyclerView分离的item的无效、未移除、未更新的holder。因为RecyclerView在onLayout的时候,会先把children全部移除掉,再重新添加进入,mAttachedScrap临时保存这些holder复用。 -
mChangedScrap:
mChangedScrap和mAttachedScrap类似,不参与滑动时的回收复用,只是用作临时保存的变量,它只会负责保存重新布局时发生变化的item的无效、未移除的holder,那么会重走adapter绑定数据的方法。 -
mCachedViews :
用于保存最新被移除(remove)的ViewHolder,已经和RecyclerView分离的视图;它的作用是滚动的回收复用时如果需要新的ViewHolder时,精准匹配(根据position/id判断)是不是原来被移除的那个item;如果是,则直接返回ViewHolder使用,不需要重新绑定数据;如果不是则不返回,再去mRecyclerPool中找holder实例返回,并重新绑定数据。这一级的缓存是有容量限制的,最大数量为2。 -
mViewCacheExtension:
RecyclerView给开发者预留的缓存池,开发者可以自己拓展回收池,一般不会用到,用RecyclerView系统自带的已经足够了。 -
mRecyclerPool:
是一个终极回收站,真正存放着被标识废弃(其他池都不愿意回收)的ViewHolder的缓存池,如果上述mAttachedScrap、mChangedScrap、mCachedViews、mViewCacheExtension都找不到ViewHolder的情况下,就会从mRecyclerPool返回一个废弃的ViewHolder实例,但是这里的ViewHolder是已经被抹除数据的,没有任何绑定的痕迹,需要重新绑定数据。它是根据itemType来存储的,是以SparseArray嵌套一个ArraryList的形式保存ViewHolder的。
四级复用:
Scrap:最轻量级的复用,包含
mAttachedScrap
和mAttachedScrap
两个部分,Scrap不会参与滑动时的回收复用,作为重新布局的临时缓存,当RV重新布局时,mAttachedScrap负责保存其中没有改变的ViewHolder;剩下的由mChangedScrap负责保存,布局结束时,Scrap列表应该是空的,缓存的数据要么重新布局出来,要么被清空;CacheView:用于RV列表位置产生变动时,对刚刚移出屏幕的view进行回收复用。CacheView的最大容量是2,如果一直向一个方向滑动,缓存的Item超过两个,就将CacheView中第一个item放入
RecycledViewPool
中,再将新的放入CacheView,如果一直朝一个方向滚动,CacheView并没有在效率上产生帮助,它只是把后面滑过的ViewHolder缓存起来,如果经常来回滑动,那么从CacheView根据对应位置的item直接复用,不需要重新绑定数据,将会得到很好的利用。ViewCacheExtension:自定义缓存,额外提供了一层缓存池给开发者,开发者视情况而定是否使用ViewCacheExtension增加一层缓存池;
RecycledViewPool:终极回收站,在Scrap、CacheView、ViewCacheExtension都不愿意回收的时候,都会丢到RecycledViewPool中回收;
public static class RecycledViewPool {
// mScrap 的大小
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
}
SparseArray mScrap = new SparseArray<>();
}
RecycledViewPool的本质是一个SparseArray,SparseArray的value是ScrapData(存放VH的ArrayList),缓存池定义了默认的缓存大小DEFAULT_MAX_SCRAP = 5,这个数量不是说整个缓存池只能缓存这多个ViewHolder,而是不同itemType的ViewHolder的list的缓存数量,即mScrap的数量,说明最多只有5组不同类型的mScrapHeap。mMaxScrap = DEFAULT_MAX_SCRAP说明每种不同类型的ViewHolder默认保存5个,当然mMaxScrap的值是可以设置的。
SparseArray分析:避免装箱,节省空间,不需要hash映射
其实,Scrap缓存池不参与滚动的回收复用,CacheView缓存池被称为一级缓存,又因为ViewCacheExtension缓存池是给开发者定义的缓存池,一般不用到,所以RecycledViewPool缓存池被称为二级缓存,那么这样来说实际只有两层缓存。
layoutChunk()
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
...
View view = layoutState.next(recycler);
addView(view);
...
}
tryGetViewHolderForPositionByDeadline
是RV复用机制的具体实现方法,一共有四层复用池,下面结合这个方法的代码理解四层复用池:
tryGetViewHolderForPositionByDeadline() 第一步
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
...
}
ViewHolder getChangedScrapViewForPosition(int position) {
// If pre-layout, check the changed scrap for an exact match.
// find by position
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i); //
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// find by id
if (mAdapter.hasStableIds()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
final long id = mAdapter.getItemId(offsetPosition);
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i); //
if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
}
return null;
}
- 第一步判断有没有改变的VH,在
mChangedScrap
中寻找,如果没有进入第二步;
tryGetViewHolderForPositionByDeadline() 第二步
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
// 如果第一层是null 进入第二层
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
...
}
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
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) {
// This View is good to be used. We just need to unhide, detach and move to the
// scrap list.
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;
}
}
// 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;
}
}
return null;
}
- 第二步,根据position分别在scrap的mAttachedScrap、mChildHelper、mCachedViews中查找;
tryGetViewHolderForPositionByDeadline() 第三步
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
...
}
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// Look in our attached views first
final int count = mAttachedScrap.size();
for (int i = count - 1; i >= 0; i--) {
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()) {
// this might be valid in two cases:
// > item is removed but we are in pre-layout pass
// >> do nothing. return as is. make sure we don't rebind
// > item is removed then added to another position and we are in
// post layout.
// >> remove removed and invalid flags, add update flag to rebind
// because item was invisible to us and we don't know what happened in
// between.
if (!mState.isPreLayout()) {
holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
}
}
return holder;
} else if (!dryRun) {
// if we are running animations, it is actually better to keep it in scrap
// but this would force layout manager to lay it out which would be bad.
// Recycle this scrap. Type mismatch.
mAttachedScrap.remove(i);
removeDetachedView(holder.itemView, false);
quickRecycleScrapView(holder.itemView);
}
}
}
// Search the first-level cache
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder.getItemId() == id) {
if (type == holder.getItemViewType()) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
recycleCachedViewAt(i);
return null;
}
}
}
return null;
}
- 第三步,通过id在scrap的mAttachedScrap、mCachedViews中查找;
tryGetViewHolderForPositionByDeadline() 第四步
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
...
}
public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
- 第四步,在
mViewCacheExtension
中找,这是开发者自定义的复用机制;
tryGetViewHolderForPositionByDeadline() 第五步
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
}
...
}
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList scrapHeap = scrapData.mScrapHeap;
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
- 第五步,使用
mRecyclerPool
回收池,根据ViewType取出对应VH并移除回收池;
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
-
第六步,如果还没有,就创建一个新的VH;