我们知道RecyclerView 在滑动到一定数量之后回进行视图复用,那RecyclerView 是如何做到复用和回收的呢?首先我们在分析源码的时候得清楚我们要以什么目的去分析源码,像RecyclerView 复用的话,既然它是在滑动的时候复用的呢,那我们猜想它是不是就是在走滑动事件的时候去做的复用呢?带着这个疑问我们找到RecyclerView 源码里面
从生面的描述中,首先我们需要找到滑动事件然后继续找复用与回收的方法
寻找回收与复用的方法
首先从RecyclerView源码中找到滑动事件的地方:
@Override
public boolean onTouchEvent(MotionEvent e) {
//....省略代码....
switch (action) {
case MotionEvent.ACTION_DOWN: {
//....省略代码....
break;
case MotionEvent.ACTION_MOVE: {
//....省略代码....
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;
case MotionEvent.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP: {
//....省略代码....
break;
case MotionEvent.ACTION_CANCEL: {
cancelTouch();
}
break;
}
//....省略代码....
return true;
}
从代码中我们找到scrollByInternal
这个方法,然后找到scrollStep
:
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
//代码省略.....
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
//代码省略.....
}
我们看到它调用了两个方法一个是垂直方向滑动的方法,一个是水平方向滑动的方法,而这两个方法是LayoutManager
里面的,我们随便进去一个scrollVerticallyBy
:
public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
return 0;
}
发现它什么都没实现,因为我们知道LayoutManager
它是一个抽象类,我们进到它的实现类LinearLayoutManager
里面看下:
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
发现它调用了scrollBy
:
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
//省略代码...
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
//省略代码...
return scrolled;
}
我们知道了一个叫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 < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
//回收方法
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
//复用方法
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.endSection();
}
//省略代码....
return start - layoutState.mAvailable;
}
从源码中可以看到两个方法:recycleByLayoutState
和layoutChunk
;这两个就是回收与复用视图的两个方法;至此,回收与复用的方法就找到了,接下来,就分析他们是如何复用与回收的,我们先来看复用;
复用源码分析
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
//代码省略...
我们看到它调用了layoutState.next
:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
可以看到它调用了getViewForPosition
,我们点进去之后,它最终会调用tryGetViewHolderForPositionByDeadline
方法:
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
if (holder == null) {
//从mAttachedScrap与mCachedViews 缓存中拿
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount() + exceptionLabel());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
//从mViewCacheExtension缓存中拿
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder"
+ exceptionLabel());
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view." + exceptionLabel());
}
}
}
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
//从RecycledViewPool缓存中拿
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;
}
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");
}
}
}
return holder;
}
从源码中,我们可以看到RecyclerView 在复用的做了多级缓存,然后在获取缓存中的ViewHolder
视图的时候,它会一级一级的去找,从源码中我们姑且将缓存分为4级别,分别是:
- mChangedScrap与mAttachedScrap
- mCachedViews 默认最大缓存数量为2
- mViewCacheExtension
- RecycledViewPool
首先它会一级一级的去找,需要注意的是在RecycledViewPool里面拿的时候是这样拿的:
@Nullable
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;
}
看到没,它在拿RecycledViewPool里面的视图的时候,采用的是先进先出的顺序拿的,也就是说它拿的是这个list集合里面的最后一个元素,而在后面回收mCachedViews的时候,采用的又是先进后出的方式, 接下来我们看下回收的时候是怎样的;
回收源码分析
回到回收的代码,如下所示:
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
} else {
recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
}
}
发现它会根据布局的方向调用recycleViewsFromEnd
和recycleViewsFromStart
方法,我们看下recycleViewsFromStart
方法:
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
final int limit = dt;
final int childCount = getChildCount();
if (mShouldReverseLayout) {
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
// stop here
recycleChildren(recycler, childCount - 1, i);
return;
}
}
} else {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
// stop here
recycleChildren(recycler, 0, i);
return;
}
}
}
}
发现有个recycleChildren
的方法:
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
//代码省略...
if (endIndex > startIndex) {
for (int i = endIndex - 1; i >= startIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
} else {
for (int i = startIndex; i > endIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
}
}
然后又调了removeAndRecycleViewAt
:
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
removeViewAt(index);
recycler.recycleView(view);
}
发现它调用了recycler.recycleView(view)
:
public void recycleView(@NonNull View view) {
//代码省略...
recycleViewHolderInternal(holder);
}
recycleViewHolderInternal
这个方法才是真正回收的方法:
void recycleViewHolderInternal(ViewHolder holder) {
//代码省略
//判断是否需要回收
if (forceRecycle || holder.isRecyclable()) {
//判断缓存里面是否有缓存
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
//判断缓存的大小是否是缓存的最大值,其实最大值为2, 如果大于等于最大值的话,就将第一个元素回收,保证这个缓存里面的视图是最新的
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
//回收视图, 采用先进后出的方式
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
// when adding the view, skip past most recently prefetched views
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);
cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
// even if the holder is not removed, we still call this method so that it is removed
// from view holder lists.
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
从上面的源码中可以看到如果mCachedViews这个缓存的数据慢了的话,它就会调用recycleCachedViewAt
方法将mCachedViews里面的一个元素移除掉,我们来看下recycleCachedViewAt
:
void recycleCachedViewAt(int cachedViewIndex) {
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
我们可以看到在这个方法里面,它显示将视图viewHolder
添加到了RecycledViewPool里面,而这个RecycledViewPool相当于一个视图缓存池,然后将mCachedViews
里面的一个元素移除了,需要注意的是其实mCachedViews
缓存的最大值其实就是2:
这里值的注意的是在回收mCachedViews
的时候,采用的是先进后出的方式来回收的,而在上面说的复用的时候,RecycledViewPool
采用的是先进先出的方式,这是为啥呢,因为在复用的时候我们通过viewType获取到的ViewHolder是一样的,而且在通过scrapHeap.remove(scrapHeap.size() - 1)
的方式,每次取的时候也方便一些,而在回收mCachedViews
的时候由于要回收的每个ViewHolder可能是不一样的,所以要保证mCachedViews
里面的视图是最新的,所以采用了先进后出的方式;接着我们来看下,这个RecycledViewPool是怎么将视图缓存起来的:
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);
//代码省略....
holder.mOwnerRecyclerView = null;
getRecycledViewPool().putRecycledView(holder);
}
可以看到,它调用了getRecycledViewPool().putRecycledView(holder)
我们来看下putRecycledView
这个方法,看它是怎么缓存的:
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
可以看到,它调用了getScrapDataForType
这个方法,然后得到了一个数据,然后将视图ViewHolder添加到了这个数据里面, 至此寻找滑动事件来分析RecyclerView回收与复用的源码就完了,其实我们还可以根据onLayout
来分析,这里我就不一一赘述了,我就将onLayout分析的方法调用流程贴出来吧:
总结
Recycler复用与回收,其实就是根据多级缓存,回收的时候将ViewHolder放到了这些缓存里面,复用的时候一级一级的从这些缓存里面去拿!