同学们面试中有没有经常被问到RecyclerView的回收复用逻辑呢?是否又跟我一样一脸懵逼?今天我们就从源码的角度入手,扒开RecyclerView的“神秘面纱”,让面试官都见鬼去吧!
回收复用
何谓回收与复用呢?RecyclerView在滚动时,会将滑出屏幕外的View进行一个缓存,这就是ViewHolder的回收,从屏幕外面滑进来的ViewHolder呢,会从缓存中取ViewHolder,如果有缓存没有,那么就进行创建,如果找到了就进行复用。所以准确的应该说是ViewHolder的回收与复用。
ViewHolder
RecyclerView中每一条Item就会被封装成一个ViewHolder对象,我们一般通过ViewHolder进行view控件的初始化逻辑
复用
我们先来看复用流程,从哪里入手呢?RecyclerView回收与复用是在手指滑动的时候进行的,所以可以从RecyclerView的事件函数中来进行解剖,滑动事件,必然是 MotionEvent.ACTION_MOVE这个分支了。
@Override
public boolean onTouchEvent(MotionEvent e) {
// ................................
switch (action) {
case MotionEvent.ACTION_MOVE: {
// ..............................................
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 (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
// 横向滑动
if (canScrollHorizontally) {
if (dx > 0) {
dx = Math.max(0, dx - mTouchSlop);
} else {
dx = Math.min(0, dx + mTouchSlop);
}
if (dx != 0) {
startScroll = true;
}
}
// 纵向滑动
if (canScrollVertically) {
if (dy > 0) {
dy = Math.max(0, dy - mTouchSlop);
} else {
dy = Math.min(0, dy + mTouchSlop);
}
if (dy != 0) {
startScroll = true;
}
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
// 如果状态是拖动状态
if (mScrollState == SCROLL_STATE_DRAGGING) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
// 嵌套滑动处理,这里不需要关心
if (dispatchNestedPreScroll(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
mReusableIntPair, mScrollOffset, TYPE_TOUCH
)) {
dx -= mReusableIntPair[0];
dy -= mReusableIntPair[1];
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
// Scroll has initiated, prevent parents from intercepting
getParent().requestDisallowInterceptTouchEvent(true);
}
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
// 重点看这里
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e, TYPE_TOUCH)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
}
break;
}
// ...........................
return true;
}
当手指在屏幕上触发任意事件时,RecyclerView先是处理了一些嵌套滑动的事件,不过这并不是今天的重点,主要看这句:
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e, TYPE_TOUCH)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
// ......................................
if (mAdapter != null) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
scrollStep(x, y, mReusableIntPair);
// 这里省略了嵌套滑动的一些参数处理。。。。。。。
}
// ........................................
return consumedNestedScroll || consumedX != 0 || consumedY != 0;
}
代码很多,这里把无关的东西都省略了,我们发现scrollByInternal
方法中调用了scrollStep()
:
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
TraceCompat.beginSection(TRACE_SCROLL_TAG);
fillRemainingScrollValues(mState);
int consumedX = 0;
int consumedY = 0;
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
TraceCompat.endSection();
repositionShadowingViews();
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
if (consumed != null) {
consumed[0] = consumedX;
consumed[1] = consumedY;
}
}
LayoutManager
可以看到,根据横向滑动还是纵向滑动,分别调用了mLayout.scrollHorizontallyBy(dx, mRecycler, mState)
和mLayout.scrollVerticallyBy(dy, mRecycler, mState)
,回收复用逻辑都是一样的,我们来看看纵向滑动:
mLayout是什么?
LayoutManager mLayout;
原来它就是初始化RecyclerView时设置的LayoutManager
我们拿LinearLayoutManager举例:
// LinearLayoutManager.java
@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 layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDy = Math.abs(dy);
updateLayoutState(layoutDirection, absDy, true, state);
// 关键点fill()
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
// ....................
return scrolled;
}
fill()
发现它调用了fill()
方法,顾名思义,它是用来填充View的,这应该就是复用逻辑了,点进去证明一下:
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// ............................................................................
// 如果当前的position小于条目总数时,进入循环
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
layoutChunk(recycler, state, layoutState, layoutChunkResult);
// ............................................................................
// 以下是回收(缓存)逻辑
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
}
return start - layoutState.mAvailable;
}
在一个while循环中调用了layoutChunk(recycler, state, layoutState, layoutChunkResult);
方法,这个方法实际上就是在获取缓存的View,然后进行复用,并展示;因为屏幕上的数据可能会展示多条目,所以用来while循环进行多个条目的展示。我们来看一下:
layoutChunk()
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// layoutState.next(), 在recyclerview对象中查找
View view = layoutState.next(recycler);
// 下面逻辑为获取到view之后,设置view参数,并进行展示,到此复用逻辑就走完了
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;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
// 。。。。。。。。。。。。。。。。。。。。。。。。
}
可以看到,这个方法中通过layoutState.next(recycler)
获取到view之后进行展示,复用逻辑就到这里结束了,我们重点来追踪next方法:
layoutState.next(recycler)
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
mScrapList这里先不看,view是通过recycler.getViewForPosition(mCurrentPosition);
获取的。
tryGetViewHolderForPositionByDeadline
// RecyclerView
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
// 。。。。。。。。。。。。。。。。。。。。。。
}
if (holder == null) {
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
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);
}
// mViewCacheExtension是开发者自定义的缓存方案,一般用不到
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
// 。。。。。。。。。。。。。。。。。。。。。
}
}
if (holder == null) { // fallback to pool
// 从缓存池中获取
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
// 实在没找到,直接调用adapter的createViewHolder方法创建
holder = mAdapter.createViewHolder(RecyclerView.this, type);
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
}
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
+ exceptionLabel());
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
// tryBindViewHolderByDeadline方法中调用了adatper.bindViewHolder(),有兴趣的可以看看。
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
// viewholder创建完成之后,设置view的参数,并将holder返回
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
return holder;
}
缓存分级
这里可以看出,是先从缓存获取的ViewHolder, 然后直接使用ViewHolder的成员变量itemView进行返回。根据上面代码我们总结一下,获取ViewHolder分为以下几步:
(涉及到的方法大家可以自行点进去看一下,我这里直接写出来)
1. mChangedScrap
getChangedScrapViewForPosition(),我们可以把它看作第一级缓存
- 从mChangedScrap中根据position去找,找到了直接返回
- 如果根据position没有找到,那么就根据itemId来找,找到了直接返回,没找到返回null
ViewHolder getChangedScrapViewForPosition(int position) {
// If pre-layout, check the changed scrap for an exact match.
final int changedScrapSize;
// 如果mChangedScrap为空,直接返回null
if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
return null;
}
// 从mChangedScra中根据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;
}
}
// 如果根据position没有找到,那么就根据itemId来找,找到了直接返回,没找到返回null
if (mAdapter.hasStableIds()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
final long id = mAdapter.getItemId(offsetPosition);
// 遍历changedScrapSize,如果找到了直接返回
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;
}
2. mAttachedScrap & mCachedViews
如果mChangedScrap没有获取到,那么走到getScrapOrHiddenOrCachedHolderForPosition()方法,这是第二级缓存。
- 试图从mAttachedScrap中通过position寻找viewholder,找到了直接返回
- 否则从mCachedViews中查找,找不到还是返回null。
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// 是试图从mAttachedScrap中通过position寻找viewholder,找到了直接返回
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;
}
}
// 。。。。。。。。省略一些代码
// 从mCachedViews中通过position寻找viewholder,找到了直接返回,找不到返回null
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
&& !holder.isAttachedToTransitionOverlay()) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
}
}
return null;
}
-
如果mAttachedScrap和mCachedViews也没有找到呢,继续从这两个里面找,不过这次是通过id查找了。往下看还有个getScrapOrCachedViewForId()方法,所以同学们注意,这还是属于第二级缓存哦~
- 试图从mAttachedScrap中通过id寻找viewholder,找到了直接返回
- 否则从mCachedViews中查找,找不到还是返回null。
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) { // Look in our attached views first final int count = mAttachedScrap.size(); // 通过itemid,在mAttachedScrap查找 for (int i = count - 1; i >= 0; i--) { final ViewHolder holder = mAttachedScrap.get(i); if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) { if (type == holder.getItemViewType()) { //。。。。。。。。。。。。。。。。。。。。。。 return holder; } // 。。。。。。。。。 } } // 通过itemid,在mCachedViews查找 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; } // 。。。。。。。。。。。。。。。。 } } return null; }
3. mViewCacheExtension
如果还是没有找到,就走到了mViewCacheExtension.getViewForPositionAndType(this, position, type);
方法,mViewCacheExtension是开发这自定的一个缓存机制,可以通过recyclerview的方法去设置:
void setViewCacheExtension(ViewCacheExtension extension) {
mViewCacheExtension = extension;
}
不过一般我们是用不到的啦
4. mRecyclerPool
这时候就走到了ViewHolder的缓存池 getRecycledViewPool().getRecycledView(type);
@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;
}
可以看到,缓存池是获取到了最后一个ViewHolder,然后把获取到的这个从池中移除,所以这个缓存池是一个栈结构,先进后出。
找不到,直接创建
如果所有缓存中都没有找到,那么直接回调到了mAdapter.createViewHolder(RecyclerView.this, type),是不是就很熟悉了
至此,RecyclerView的复用逻辑就走完了,那么回收是怎么进行的呢?
回收(缓存)
正常流程
ViewHolder的回收也可以看作是进行缓存,因为它其实是将ViewHolder分别保存在了几个List集合中。
还记不记得View复用时走到了fill方法,View回收也是从这里出发的=
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// 省略了复用逻辑............................................................................
// 以下是回收(缓存)逻辑
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
}
return start - layoutState.mAvailable;
}
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
int scrollingOffset = layoutState.mScrollingOffset;
int noRecycleSpace = layoutState.mNoRecycleSpace;
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
}
回收时,调用到了recycleByLayoutState方法,里面有两个关键方法recycleViewsFromEnd & recycleViewsFromStart。 这里可以理解成上滑和下滑,移出屏幕的部分进行缓存。两个方法的回收逻辑都是一样的,我们随便看一个就可以了。
start为手指向上滑动时的回收
end为手指向下滑动时的回收
recycleViewsFromStart()
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
// ignore padding, ViewGroup may not clip children.
final int limit = scrollingOffset - noRecycleSpace;
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方法
recycleChildren()
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
if (startIndex == endIndex) {
return;
}
if (DEBUG) {
Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
}
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()
// RecyclerView.java
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
removeViewAt(index);
recycler.recycleView(view);
}
先从ViewGroup中移除了这个view, 然后通过recycleView(view)方法进行回收
recycleView(view)
public void recycleView(@NonNull View view) {
// 1. 根据View获取ViewHolder
ViewHolder holder = getChildViewHolderInt(view);
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
// 2. 回收holder
recycleViewHolderInternal(holder);
// 结束item动画效果
if (mItemAnimator != null && !holder.isRecyclable()) {
mItemAnimator.endAnimation(holder);
}
}
recycleViewHolderInternal(holder)
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
// 1. 首先是缓存到了mCachedViews中
// 注意这里有个小逻辑,cachedViewSize = DEFAULT_CACHE_SIZE = 2.
// 也就是说,如果缓存前,mCachedViews中有了2个View了,那么就先移除掉第一个View,将它移入缓存池中
// 看下面的recycleCachedViewAt(0);方法
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)) {
// 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;
}
// 2. mCachedViews默认最多放入2条数据,如果有2条了,那么就先移除第一条,然后再add到后面。
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
//。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
recycleCachedViewAt()
view进行缓存时,这里传入了0。看下面的代码,就是移除了mCachedViews中的数据,并将其put到了缓存池。
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);
View itemView = holder.itemView;
// 。。。。。。。。。。。。。。。。。。。。。。。。
// 分发一些回收事件
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
holder.mOwnerRecyclerView = null;
// 将holder添加到缓存池
getRecycledViewPool().putRecycledView(holder);
}
putRecycledView()
public void putRecycledView(ViewHolder scrap) {
// 1. 先获取当前holder的viewType。
final int viewType = scrap.getItemViewType();
// 2. 获取缓存池中相同viewType的数量
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
// 3. mMaxScrap = DEFAULT_MAX_SCRAP = 5.
// 也就是说,如果缓存池中相同viewType的数量超过了5条,那么就直接将此holder抛弃,不进行缓存。
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
// 4. 将holder中的数据全部清空,以便后面的复用
scrap.resetInternal();
// 5. 将holder放入mScrapHeap中,也就是缓冲池
scrapHeap.add(scrap);
}
根据上面的逻辑我们可以看到,缓存池中,相同的ViewType最多缓存5条数据。那么总结一下, mCacheViews和Pool是怎么工作的呢?
首先会判断mCacheViews的数量,如果超过2条,那么先移除第1条数据,让后将holder存入到里面,并将移除的这条数据放入缓存池中;同时,缓存池会判断当前holder的ViewType,如果相同的ViewType数量超过了5条,那么直接将此holder抛弃,不进行处理,否则才会将holder存入到池中,所以它跟CacheViews相反,是一个先进后出的结构
到这,recycleViewHolderInternal()方法的流程已经执行完了,不对啊,mChangedScrap和mAttachedScrap 去哪了呢?不要急
onLayout
我们从复用逻辑可以看到,获取到view之后通过addView添加的RecyclerView中了,这时必然会调用到onLayout布局的方法中,我们来看一下:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
void dispatchLayout() {
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
可以看到用到了分发layout,dispatchLayoutStep1,dispatchLayoutStep2,dispatchLayoutStep3,3个方法,无论哪个方法,都势必会调用到mLayout.onLayoutChildren(mRecycler, mState);
onLayoutChildren
// LinearLayoutManager
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
// 如果总条目为0 ,移除所有view
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
}
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
// 1. 回收逻辑
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
// 2. 复用view
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
// 2. 复用view
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtra = extraForStart;
// 2. 复用view
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
// 2. 复用view
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
final int lastElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
// 2. 复用view
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForEnd = mLayoutState.mAvailable;
// start could not consume all it should. add more items towards end
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtra = extraForEnd;
// 2. 复用view
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}
// 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
发现它通过detachAndScrapAttachedViews方法调用了scrapOrRecycleView
detachAndScrapAttachedViews(recycler);
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
// 如果当前viewHolder没有失效并且没有被移除,也就是说不是通过删除此条目,而是滑出了屏幕的情况下
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
// 这里调用了recycleViewHolderInternal,上面正常流程的缓存逻辑
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 否则调用到scrapView
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
我们现在主要看recycler.scrapView(view);
scrapView()
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
到这里清晰了吧,mAttachedScrap.add(holder);和mChangedScrap.add(holder); 。 所以只有当条目被移除之后,才会缓存到这两个里面。
fill()
当缓存完成之后,onLayoutChildren(*)方法继续调用了fill进行复用并展示在屏幕上。
流程图
只看源码有些枯燥,抛个时序图给大家吧
回收流程图
复用流程
留疑
通过回收逻辑我们知道,如果相同的ViewType超过5条时,那么ViewHolder不会缓存,复用不成立就会创建。所以当GridLayoutManger的SapnSize大于5时,ViewHolder是否就会在滚动的过程中一直创建呢?如果是,有没有办法解决这个问题呢?这些东西抛给道友们自己去实践吧!我相信如果仔细看完这篇文章,心中都会有个答案。