分析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;
}
滑動的監(jiān)聽里,調(diào)用了scrollByInternal()
內(nèi)部封裝的一個滑動方法:
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;
}
在內(nèi)部滑動方法中给梅,調(diào)用了mLayout.scrollHorizontallyBy()/mLayout.scrollVerticallyBy()
假丧;
@VisibleForTesting LayoutManager mLayout;
這是RV的一個成員變量,使用setLayoutManager設(shè)置动羽,RV將他的布局的操作都封裝到了LayoutManager中包帚,常見的有LinearLayoutManager,GridLayoutManager运吓;
LinearLayoutManager的實現(xiàn)
/**
* {@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);
// 復(fù)用
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
fill()中有兩個核心方法渴邦,回收和復(fù)用疯趟;
回收機制
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) {
...
// 觸發(fā)Adapter和RV層設(shè)置的回調(diào)
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();
// 根據(jù)viewType取出SparseArray中的list
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
scrap.resetInternal();
// 添加到list中
scrapHeap.add(scrap);
}
復(fù)用機制
Recycler類
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
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復(fù)用機制的核心實現(xiàn)類,設(shè)計了四級緩存谋梭,優(yōu)先級順序是:Scrap信峻、CacheView、ViewCacheExtension瓮床、RecycledViewPool
-
mAttachedScrap:
不參與滑動時的回收復(fù)用盹舞,只保存重新布局時從RecyclerView分離的item的無效、未移除纤垂、未更新的holder矾策。因為RecyclerView在onLayout的時候,會先把children全部移除掉峭沦,再重新添加進入贾虽,mAttachedScrap臨時保存這些holder復(fù)用。 -
mChangedScrap:
mChangedScrap和mAttachedScrap類似吼鱼,不參與滑動時的回收復(fù)用蓬豁,只是用作臨時保存的變量,它只會負責(zé)保存重新布局時發(fā)生變化的item的無效菇肃、未移除的holder地粪,那么會重走adapter綁定數(shù)據(jù)的方法。 -
mCachedViews :
用于保存最新被移除(remove)的ViewHolder琐谤,已經(jīng)和RecyclerView分離的視圖蟆技;它的作用是滾動的回收復(fù)用時如果需要新的ViewHolder時,精準(zhǔn)匹配(根據(jù)position/id判斷)是不是原來被移除的那個item斗忌;如果是质礼,則直接返回ViewHolder使用,不需要重新綁定數(shù)據(jù)织阳;如果不是則不返回眶蕉,再去mRecyclerPool中找holder實例返回,并重新綁定數(shù)據(jù)唧躲。這一級的緩存是有容量限制的造挽,最大數(shù)量為2。 -
mViewCacheExtension:
RecyclerView給開發(fā)者預(yù)留的緩存池弄痹,開發(fā)者可以自己拓展回收池饭入,一般不會用到,用RecyclerView系統(tǒng)自帶的已經(jīng)足夠了肛真。 -
mRecyclerPool:
是一個終極回收站圣拄,真正存放著被標(biāo)識廢棄(其他池都不愿意回收)的ViewHolder的緩存池,如果上述mAttachedScrap毁欣、mChangedScrap庇谆、mCachedViews岳掐、mViewCacheExtension都找不到ViewHolder的情況下,就會從mRecyclerPool返回一個廢棄的ViewHolder實例饭耳,但是這里的ViewHolder是已經(jīng)被抹除數(shù)據(jù)的串述,沒有任何綁定的痕跡,需要重新綁定數(shù)據(jù)寞肖。它是根據(jù)itemType來存儲的纲酗,是以SparseArray嵌套一個ArraryList的形式保存ViewHolder的。
四級復(fù)用:
Scrap:最輕量級的復(fù)用新蟆,包含
mAttachedScrap
和mAttachedScrap
兩個部分觅赊,Scrap不會參與滑動時的回收復(fù)用,作為重新布局的臨時緩存琼稻,當(dāng)RV重新布局時吮螺,mAttachedScrap負責(zé)保存其中沒有改變的ViewHolder;剩下的由mChangedScrap負責(zé)保存帕翻,布局結(jié)束時鸠补,Scrap列表應(yīng)該是空的,緩存的數(shù)據(jù)要么重新布局出來嘀掸,要么被清空紫岩;CacheView:用于RV列表位置產(chǎn)生變動時,對剛剛移出屏幕的view進行回收復(fù)用睬塌。CacheView的最大容量是2泉蝌,如果一直向一個方向滑動,緩存的Item超過兩個揩晴,就將CacheView中第一個item放入
RecycledViewPool
中勋陪,再將新的放入CacheView,如果一直朝一個方向滾動文狱,CacheView并沒有在效率上產(chǎn)生幫助粥鞋,它只是把后面滑過的ViewHolder緩存起來缘挽,如果經(jīng)常來回滑動瞄崇,那么從CacheView根據(jù)對應(yīng)位置的item直接復(fù)用,不需要重新綁定數(shù)據(jù)壕曼,將會得到很好的利用苏研。ViewCacheExtension:自定義緩存,額外提供了一層緩存池給開發(fā)者腮郊,開發(fā)者視情況而定是否使用ViewCacheExtension增加一層緩存池摹蘑;
RecycledViewPool:終極回收站,在Scrap轧飞、CacheView衅鹿、ViewCacheExtension都不愿意回收的時候撒踪,都會丟到RecycledViewPool中回收;
public static class RecycledViewPool {
// mScrap 的大小
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
}
RecycledViewPool的本質(zhì)是一個SparseArray,SparseArray的value是ScrapData(存放VH的ArrayList)大渤,緩存池定義了默認的緩存大小DEFAULT_MAX_SCRAP = 5制妄,這個數(shù)量不是說整個緩存池只能緩存這多個ViewHolder,而是不同itemType的ViewHolder的list的緩存數(shù)量泵三,即mScrap的數(shù)量耕捞,說明最多只有5組不同類型的mScrapHeap。mMaxScrap = DEFAULT_MAX_SCRAP說明每種不同類型的ViewHolder默認保存5個烫幕,當(dāng)然mMaxScrap的值是可以設(shè)置的俺抽。
SparseArray分析:避免裝箱,節(jié)省空間较曼,不需要hash映射
其實磷斧,Scrap緩存池不參與滾動的回收復(fù)用,CacheView緩存池被稱為一級緩存诗芜,又因為ViewCacheExtension緩存池是給開發(fā)者定義的緩存池瞳抓,一般不用到,所以RecycledViewPool緩存池被稱為二級緩存伏恐,那么這樣來說實際只有兩層緩存孩哑。
layoutChunk()
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
...
View view = layoutState.next(recycler);
addView(view);
...
}
tryGetViewHolderForPositionByDeadline
是RV復(fù)用機制的具體實現(xiàn)方法,一共有四層復(fù)用池翠桦,下面結(jié)合這個方法的代碼理解四層復(fù)用池:
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;
}
- 第二步丛晌,根據(jù)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
中找桐经,這是開發(fā)者自定義的復(fù)用機制毁兆;
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<ViewHolder> scrapHeap = scrapData.mScrapHeap;
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
- 第五步,使用
mRecyclerPool
回收池阴挣,根據(jù)ViewType取出對應(yīng)VH并移除回收池气堕;
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
-
第六步,如果還沒有,就創(chuàng)建一個新的VH茎芭;