目录
- 1.缓存的使用流程源码分析-滑动入口
- 2.RecyclerView的缓存流程
- 3.RecyclerView缓存总结
RecyclerView是一个非常重要的控件,是任何一个研发都需要掌握的,这个控件的设计也是非常优秀的,值得我们去学习。RecyclerView的核心就是缓存机制,RecyclerView为了提升效率使用了4级缓存:
- mChangeScrap与 mAttachedScrap:用来缓存还在屏幕内的 ViewHolder,是ViewHolder的ArrayList 集合。
- mCacheView:缓存将要隐藏ViewHolder 下次将要显示的ViewHolder 先从这个缓存里边获取,也是ViewHolder的 ArrayList 集合。
- mViewChcheExtension:需要用户自己实现的缓存,这一级系统会调用一个抽象方法,这个方法需要用户自己实现。
- mRecyclerPool:缓存池 ,这个用户根据不同的ViewType保存缓存池 ,这个缓存池是一个二维数组 外部是ScrapData 的SparseArray数组,内部是ArrayList数组。
1.缓存的使用流程源码分析-滑动入口
当用户在滑动Item的时候会进行ViewHolder的复用,下面来看滑动方法:RecyclerView的onTouchEvent方法case MotionEvent.ACTION_MOVE
public boolean onTouchEvent(MotionEvent e) { | |
if (mLayoutFrozen || mIgnoreMotionEventTillDown) { | |
return false; | |
} | |
if (dispatchOnItemTouch(e)) { | |
cancelTouch(); | |
return true; | |
} | |
if (mLayout == null) { | |
return false; | |
} | |
... | |
switch (action) { | |
case MotionEvent.ACTION_DOWN: | |
... | |
case MotionEvent.ACTION_MOVE: { | |
... | |
if (mScrollState == SCROLL_STATE_DRAGGING) { | |
mLastTouchX = x - mScrollOffset[]; | |
mLastTouchY = y - mScrollOffset[]; | |
//入口在这里 因为滑动的时候会发生缓存操作 所以一个入口在这里 | |
if (scrollByInternal( | |
canScrollHorizontally ? dx :, | |
canScrollVertically ? dy :, | |
vtev)) { | |
getParent().requestDisallowInterceptTouchEvent(true); | |
} | |
if (mGapWorker != null && (dx != || dy != 0)) { | |
mGapWorker.postFromTraversal(this, dx, dy); | |
} | |
} | |
} break; | |
case MotionEvent.ACTION_POINTER_UP: { | |
onPointerUp(e); | |
} break; | |
case MotionEvent.ACTION_UP: | |
... | |
vtev.recycle(); | |
return true; | |
} |
scrollByInternal 方法就是使用缓存的入口方法
下面来看scrollByInternal方法
boolean scrollByInternal(int x, int y, MotionEvent ev) { | |
int unconsumedX =, unconsumedY = 0; | |
int consumedX =, consumedY = 0; | |
consumePendingUpdateOperations(); | |
if (mAdapter != null) { | |
eatRequestLayout(); | |
onEnterLayoutOrScroll(); | |
Trace.beginSection(TRACE_SCROLL_TAG); | |
if (x !=) { | |
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState); | |
unconsumedX = x - consumedX; | |
} | |
if (y !=) { | |
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState); | |
unconsumedY = y - consumedY; | |
} | |
Trace.endSection(); | |
repositionShadowingViews(); | |
onExitLayoutOrScroll(); | |
resumeRequestLayout(false); | |
} | |
... | |
return consumedX != || consumedY != 0; | |
} |
这里区分横向和纵向滑动:scrollHorizontallyBy与scrollVerticallyBy
下面分析纵向滑动的情况scrollVerticallyBy(横向类似):
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, | |
RecyclerView.State state) { | |
if (mOrientation == HORIZONTAL) { | |
return; | |
} | |
return scrollBy(dy, recycler, state); | |
} |
这里调用了scrollBy方法,继续往下跟
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { | |
if (getChildCount() == || dy == 0) { | |
return; | |
} | |
mLayoutState.mRecycle = true; | |
ensureLayoutState(); | |
final int layoutDirection = dy > ? 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 <) { | |
if (DEBUG) { | |
Log.d(TAG, "Don't have any more elements to scroll"); | |
} | |
return; | |
} | |
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,当布局或者上下滚动的时候会调用fill方法。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, | |
RecyclerView.State state, boolean stopOnFocusable) { //布局或者上下滚动的时候会调用 | |
// max offset we should set is mFastScroll + available | |
final int start = layoutState.mAvailable; | |
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { | |
// TODO ugly bug fix. should not happen | |
if (layoutState.mAvailable <) { | |
layoutState.mScrollingOffset += layoutState.mAvailable; | |
} | |
recycleByLayoutState(recycler, layoutState); //回收ViewHolder | |
} | |
int remainingSpace = layoutState.mAvailable + layoutState.mExtra; | |
LayoutChunkResult layoutChunkResult = mLayoutChunkResult; | |
while ((layoutState.mInfinite || remainingSpace >) && layoutState.hasMore(state)) { | |
layoutChunkResult.resetInternal(); | |
layoutChunk(recycler, state, layoutState, layoutChunkResult); //循环调用 这里是layout的核心 | |
if (layoutChunkResult.mFinished) { | |
break; | |
} | |
... | |
} | |
if (DEBUG) { | |
validateChildOrder(); | |
} | |
return start - layoutState.mAvailable; | |
} |
layoutChunk这个方法是使用缓存的入口,recycleByLayoutState这个是进行ViewHolder缓存的入口。
下面来看layoutChunk:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, | |
LayoutState layoutState, LayoutChunkResult result) { | |
View view = layoutState.next(recycler); | |
if (view == null) { | |
if (DEBUG && layoutState.mScrapList == null) { | |
throw new RuntimeException("received null view when unexpected"); | |
} | |
// if we are laying out views in scrap, this may return null which means there is | |
// no more items to layout. | |
result.mFinished = true; | |
return; | |
} | |
... | |
result.mFocusable = view.isFocusable(); | |
} |
这个方法里边调用了layoutState的next方法得到一个View,那么关键就是next方法了
View next(RecyclerView.Recycler recycler) { | |
if (mScrapList != null) { | |
return nextViewFromScrapList(); | |
} | |
final View view = recycler.getViewForPosition(mCurrentPosition); | |
mCurrentPosition += mItemDirection; | |
return view; | |
} | |
public View getViewForPosition(int position) { | |
return getViewForPosition(position, false); | |
} | |
View getViewForPosition(int position, boolean dryRun) { | |
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; | |
} |
这个方法又调用了recycler.getViewForPosition方法,最终调到了tryGetViewHolderForPositionByDeadline这个方法。
下面来分析tryGetViewHolderForPositionByDeadline这个方法,整个ViewHolder的复用流程都在这里,这里是最核心的位置:
ViewHolder tryGetViewHolderForPositionByDeadline(int position, | |
boolean dryRun, long deadlineNs) { | |
if (position < || position >= mState.getItemCount()) { | |
throw new IndexOutOfBoundsException("Invalid item position " + position | |
+ "(" + position + "). Item count:" + mState.getItemCount()); | |
} | |
boolean fromScrapOrHiddenOrCache = false; | |
ViewHolder holder = null; | |
//) If there is a changed scrap, try to find from there | |
if (mState.isPreLayout()) { //通过位置从mChangeScrap缓存中获取ViewHolder | |
holder = getChangedScrapViewForPosition(position); | |
fromScrapOrHiddenOrCache = holder != null; | |
} | |
//) Find by position from scrap/hidden list/cache | |
if (holder == null) {//通过position的方式从mAttachScrap或者mCacheViews中获取ViewHolder | |
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); | |
... | |
} | |
if (holder == null) { | |
final int offsetPosition = mAdapterHelper.findPositionOffset(position); | |
if (offsetPosition < || offsetPosition >= mAdapter.getItemCount()) { | |
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " | |
+ "position " + position + "(offset:" + offsetPosition + ")." | |
+ "state:" + mState.getItemCount()); | |
} | |
final int type = mAdapter.getItemViewType(offsetPosition); | |
//) Find from scrap/cache via stable ids, if exists | |
if (mAdapter.hasStableIds()) { | |
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), | |
type, dryRun); //通过id的方式从mAttachScrap或者mCacheViews中获取ViewHolder | |
if (holder != null) { | |
// update position | |
holder.mPosition = offsetPosition; | |
fromScrapOrHiddenOrCache = true; | |
} | |
} | |
if (holder == null && mViewCacheExtension != null) { //从用户自定义缓存获取ViewHolder | |
// We are NOT sending the offsetPosition because LayoutManager does not | |
// know it. | |
final View view = mViewCacheExtension | |
.getViewForPositionAndType(this, position, type); | |
... | |
} | |
if (holder == null) { // 从缓存池获取ViewHolder | |
if (DEBUG) { | |
Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" | |
+ position + ") fetching from shared pool"); | |
} | |
holder = getRecycledViewPool().getRecycledView(type); | |
if (holder != null) { | |
holder.resetInternal(); | |
if (FORCE_INVALIDATE_DISPLAY_LIST) { | |
invalidateDisplayListInt(holder); | |
} | |
} | |
} | |
if (holder == null) { | |
long start = getNanoTime(); | |
if (deadlineNs != FOREVER_NS | |
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { | |
// abort - we have a deadline we can't meet | |
return null; | |
} //如果还是获取不到ViewHolder,那么就需要通过createViewHolder创建了 | |
holder = mAdapter.createViewHolder(RecyclerView.this, type); | |
if (ALLOW_THREAD_GAP_WORK) { | |
// only bother finding nested RV if prefetching | |
RecyclerView innerView = findNestedRecyclerView(holder.itemView); | |
if (innerView != null) { | |
holder.mNestedRecyclerView = new WeakReference<>(innerView); | |
} | |
} | |
long end = getNanoTime(); | |
mRecyclerPool.factorInCreateTime(type, end - start); | |
if (DEBUG) { | |
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); | |
} | |
} | |
} | |
... | |
boolean bound = false; | |
if (mState.isPreLayout() && holder.isBound()) { | |
// do not update unless we absolutely have to. | |
holder.mPreLayoutPosition = position; | |
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { | |
if (DEBUG && holder.isRemoved()) { | |
throw new IllegalStateException("Removed holder should be bound and it should" | |
+ " come here only in pre-layout. Holder: " + holder); | |
} | |
final int offsetPosition = mAdapterHelper.findPositionOffset(position); | |
//这里会调用到onBindViewHolder方法进行数据的绑定 | |
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); | |
} | |
... | |
return holder; | |
} |
- getChangedScrapViewForPosition:通过位置从mChangeScrap缓存中获取ViewHolder。
- getScrapOrHiddenOrCachedHolderForPosition:通过position的方式从mAttachScrap或者mCacheViews中获取ViewHolder。
- getScrapOrCachedViewForId:通过id的方式从mAttachScrap或者mCacheViews中获取ViewHolder
- mViewCacheExtension.getViewForPositionAndType:从用户自定义缓存获取ViewHolder(这里系统未做实现,需要用户自定义)
- getRecycledViewPool().getRecycledView(type):从缓存池获取ViewHolder
- mAdapter.createViewHolder:如果从各个缓存中获取不到ViewHolder,那么就需要通过createViewHolder创建了
- tryBindViewHolderByDeadline:这里会调用到onBindViewHolder方法进行数据的绑定