1、开篇
在上一篇文章说到,LineaLayoutManager在填充布局之前会先调用detachAndScrapAttachedViews方法先暂时回收子View,然后在layoutChunk中进行子View的获取(可能会创建View)、测量、布局以及回收流程。实际上三大LayoutManager的流程都是一样的,只是细节上有所差异,感兴趣的同学可以阅读一下源码看看。本篇中涉及LayoutManager的部分依旧会以LinearLayoutManager为例。
回顾一下本系列要解决的问题:
- 既然是个ViewGroup,那少不了要问上一句:它的measure、layout和draw是怎么样的?
- RecyclerView是怎么回收View的?什么时候回收?
- 怎么支持多类型Item的?怎么缓存和查找的呢?
- Adapter的onRecreateViewHolder和onBindViewHolder两大核心方法是什么时候调用的?
- Item动画过程中notifyXXXChange会不会导致动画的错位?
问题1上篇文章已经解决了,而本篇要解决其中的234。
2、LayoutManager#detachAndScrapAttachedViews
detachAndScrapAttachedViews是基类LayoutManager定义的方法,如下所示:
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);
// shouldIgnore()为true表示ViewHolder不应该被回收
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
// isInvalid()为true表示Item的数据发生变化了(notifyDataSetChanged),需要重新绑定
// isRemoved()为true表示Item被移除了
// hasStableIds()为true表示每个Item会拥有一个唯一的long类型的id
// 我们可以通过Adapter.setHasStableIds(true)并重写Adapter.getItemId(int)方法来指定每个Item的id
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
detachAndScrapAttachedViews遍历了子View,挨个调用了scrapOrRecycleView方法进行回收。scrapOrRecycleView方法对不同情况的View进行了不同的回收处理:
- viewHolder.shouldIgnore()为true:不回收
- 数据发生变化,但是Item没有被移除且没有指定ItemId
- 其他情况
2.1 数据发生变化,但是Item没有被移除且没有指定ItemId
不回收的情况就不用多说了,来看一下第二种情况。先看removeViewAt(index)方法
LayoutManager#removeViewAt:
public void removeViewAt(int index) {
final View child = getChildAt(index);
if (child != null) {
mChildHelper.removeViewAt(index);
}
}
ChildHelper#removeViewAt:
void removeViewAt(int index) {
final int offset = getOffset(index);
final View view = mCallback.getChildAt(offset);
if (view == null) {
return;
}
if (mBucket.remove(offset)) {
unhideViewInternal(view);
}
mCallback.removeViewAt(offset);
if (DEBUG) {
Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this);
}
}
可以看到,最终是调用了ChildHelper的mCallback.removeViewAt方法。这个mCallback是ChildHelper的构造方法传入的,它的实例化在RecyclerView#initChildrenHelper中:
private void initChildrenHelper() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
...
@Override
public void removeViewAt(int index) {
final View child = RecyclerView.this.getChildAt(index);
if (child != null) {
dispatchChildDetached(child);
// Clear any android.view.animation.Animation that may prevent the item from
// detaching when being removed. If a child is re-added before the
// lazy detach occurs, it will receive invalid attach/detach sequencing.
// 清除View的动画
child.clearAnimation();
}
RecyclerView.this.removeViewAt(index);
}
...
});
}
所以在这种情况下,子View真的从RecyclerView中移除了。那么再来看Recycler的recycleViewHolderInternal方法对这个View已经被移除的ViewHolder做了些什么事情
void recycleViewHolderInternal(ViewHolder holder) {
// 对holder进行各种状态检查,这段代码省略
...
// 强制回收判断
// transientStatePreventsRecycling如果是true代表View处于某种临时状态(执行动画等)不能被回收
// 这时候可以通过重写Adapter.onFailedToRecycleView方法来解除这个临时状态并返回true以达到可以被回收的目的
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
@SuppressWarnings("unchecked")
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
if (DEBUG && mCachedViews.contains(holder)) {
throw new IllegalArgumentException("cached view received recycle internal? "
+ holder + exceptionLabel());
}
if (forceRecycle || holder.isRecyclable()) { // 强制回收或者holder是可回收的
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { // 注意这里的flag,没有这些flag的才会走接下来的回收流程
// 从这里可以看出mViewCacheMax是mCachedViews的存储数量上限
// 如果达到了这个上限,先移除最老的
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
... // 省略targetCacheIndex计算逻辑
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
// 注意:滚动时,如果View在执行动画的过程中可能会回收失败
// 这种情况下,ItemAnimatorRestoreListener#onAnimationFinished回调中回收此View
// 请考虑取消滚动出去时取消View的动画,以使View更快地被回收到回收池
if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are still removing it from animation lists"
+ exceptionLabel());
}
}
// even if the holder is not removed, we still call this method so that it is removed
// from view holder lists.
// 即使holder没有被移除/回收,也从list中先行移除
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
...
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
holder.mOwnerRecyclerView = null;
getRecycledViewPool().putRecycledView(holder);
}
这里可以看出,mViewCacheMax<=0或者有以下几个flag之一的情况是不会进行holder的回收的
ViewHolder.FLAG_INVALID
ViewHolder.FLAG_REMOVED
ViewHolder.FLAG_UPDATE
ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN
回收的时候,会优先回收到mCachedViews中,mCachedViews是一个ViewHolder的ArrayList。而RecycledViewPool是一个比较简单的对象。注意,我们当前情况是有FLAG_INVALID这个flag的,所以就当前情况是不会回收到mCachedViews中的。
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
for (int i = scrapHeap.size() - 1; i >= 0; i--) {
if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
return scrapHeap.remove(i);
}
}
}
return null;
}
...
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);
}
...
}
其中ScrapData简单封装了一个ArrayList,而RecycledViewPool封装了一个SparseArray,key是viewType,value是ScrapData。也就是说,在RecyclerViewPool里面,ViewHolder是按照viewType分门别类地存储的。
2.2 其他情况
其他情况调用如下:
LayoutManager#scrapOrRecycleView:
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
...
else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
先看LayoutManager的detachViewAt
public void detachViewAt(int index) {
detachViewInternal(index, getChildAt(index));
}
private void detachViewInternal(int index, @NonNull View view) {
if (DISPATCH_TEMP_DETACH) {
ViewCompat.dispatchStartTemporaryDetach(view);
}
mChildHelper.detachViewFromParent(index);
}
mChildHelper最终其实也是调用了mCallback的相关回调
private void initChildrenHelper() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
...
@Override
public void detachViewFromParent(int offset) {
final View view = getChildAt(offset);
if (view != null) {
final ViewHolder vh = getChildViewHolderInt(view);
if (vh != null) {
...
// 加一个flag: FLAG_TMP_DETACHED
vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
}
}
RecyclerView.this.detachViewFromParent(offset);
}
...
});
}
最终调用了RecyclerView的detachViewFromParent。
再看Recycler的scrapView
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
...
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
viewHolder.getUnmodifiedPayloads());
}
被标记为具有FLAG_REMOVED或者FLAG_INVALID、或者没有更新的以及可重用的ViewHolder被添加到了mAttachedScrap中,其余的被添加到了mChangedScrap中。这两个变量都是存放ViewHolder的ArrayList。
2.4 总结
现在来总结一下LayoutManager#detachAndScrapAttachedViews中的主要工作:
- 如果ViewHolder.shouldIgnore()返回true,忽略
- 如果ViewHolder.isInvalid()返回true且没有被移除也没有有效的id的时候,优先考虑缓存到mCachedViews里,mCachedViews达到设定的上限后,老的ViewHolder缓存到RecycledViewPool
- 其余情况,被标记为具有FLAG_REMOVED或者FLAG_INVALID、或者没有更新的以及可重用的ViewHolder被添加到了mAttachedScrap中,其余的被添加到了mChangedScrap中
3、LayoutState#next
看一下LinearLayoutManager.LayoutState的next方法
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
mScrapList是在layoutForPredictiveAnimations中被赋值的,由于layoutForPredictiveAnimations的执行在fill之后,这里我们姑且把mScrapList当作null,尽管它不一定总是null,主要看怎么从recycler中取出对应的View的。
Recycler中的相关方法:
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) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount()
+ exceptionLabel());
}
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
// 预布局的时候从mChangedScrap中查找ViewHolder
if (mState.isPreLayout()) { // 预布局
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
// 1)先尝试通过position从mAttachedScrap、mChildHeppler的hidden列表和mCachedViews里查找
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// 不能复用了,回收这个holder
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) 在通过id查找mAttachedScrap、mChildHeppler的hidden列表和mCachedViews
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());
}
}
}
// 最后才从RecycledViewPool中查找
if (holder == null) { // fallback to pool
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);
}
}
}
// 经过n此查找依然没有找到合适的ViewHolder,才通过Adapter创建新的ViewHolder
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做了保存
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");
}
}
}
// This is very ugly but the only place we can grab this information
// before the View is rebound and returned to the LayoutManager for post layout ops.
// We don't need this in pre-layout since the VH is not updated by the LM.
if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (mState.mRunSimpleAnimations) {
int changeFlags = ItemAnimator
.buildAdapterChangeFlagsForAnimations(holder);
changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
holder, changeFlags, holder.getUnmodifiedPayloads());
recordAnimationInfoIfBouncedHiddenView(holder, info);
}
}
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);
// 尝试绑定ViewHolder
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
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;
}
// 这里itemView通过持有LayoutParams间接持有了ViewHolder
// 这样我们可以通过itemView找到对应的ViewHolder,也可以通过ViewHolder找到itemView
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
return holder;
}
ViewHolder getChangedScrapViewForPosition(int position) {
// If pre-layout, check the changed scrap for an exact match.
final int changedScrapSize;
if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
return null;
}
// 先通过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;
}
}
// 通过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;
}
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
// 先查找mAttachedScrap
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())) {
// 标记FLAG_RETURNED_FROM_SCRAP
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
if (!dryRun) { // 根绝上面的代码,dryRun为false
// 这些被标记为hidden的View存储在ChildHelper中的mHiddenViews里
// 这些View所谓的hidden并不是指View的visibility,而是它们不能被某些常规方法访问
// 这里我们不深究
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);
// 标记FLAG_RETURNED_FROM_SCRAP和FLAG_BOUNCED_FROM_HIDDEN_LIST
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
// 从mCachedViews里查找
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);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
return holder;
}
}
return null;
}
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// 依然是先查找mAttachedScrap
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.
// 这里可能会是以下两种情况之一:
// item被移除了,但是当前是预布局
// item被移除后又被添加到了其他的位置
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更好,但是这会导致LayoutManager会对它进行布局,这又不太好
// 回收掉,因为viewType变了
mAttachedScrap.remove(i);
removeDetachedView(holder.itemView, false);
quickRecycleScrapView(holder.itemView);
}
}
}
// 从mCachedViews查找
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder.getItemId() == id && !holder.isAttachedToTransitionOverlay()) {
if (type == holder.getItemViewType()) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
recycleCachedViewAt(i);
return null;
}
}
}
return null;
}
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
holder.mOwnerRecyclerView = RecyclerView.this;
final int viewType = holder.getItemViewType();
long startBindNs = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
// abort - we have a deadline we can't meet
return false;
}
// 绑定ViewHolder
mAdapter.bindViewHolder(holder, offsetPosition);
long endBindNs = getNanoTime();
mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
attachAccessibilityDelegateOnBind(holder);
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
return true;
}
这段代码比较长,希望看到这里的同学耐心看完。总结一下要点:
- 预布局的时候,优先查找mChangedScrap,先position后id
- 先尝试通过position从mAttachedScrap、mChildHeppler的hidden列表和mCachedViews里查找ViewHolder
- 通过id从mAttachedScrap和mCachedViews里查找ViewHolder
- 从mViewCacheExtension查找ViewHolder
- 通过type从RecycledViewPool中查找ViewHolder
- 如果上述步骤都没有找到合适的ViewHolder,同过Adapter创建一个ViewHolder
- 根据需要绑定ViewHolder,只有needsUpdate()或者isInvalid()为true的时候才会执行ViewHolder的绑定,出现的情况有:Adapter的notifyItemChanged/notifyRangeChanged、notifyDatasetChanged相关方法以及新建ViewHolder或者从RecycledViewPool中取出ViewHolder
4、ViewHolder的回收
ViewHolder的回收(或者说缓存)有好几个地方,第一个就是上面分析的填充之前的LayoutManager#detachAndScrapAttachedViews,第二第三次分别在LinearLayoutManager#fill和RecyclerView#dispatchLayoutStep3中
先来看LinearLayoutManager#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.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
...
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);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
if (DEBUG) {
validateChildOrder();
}
return start - layoutState.mAvailable;
}
这里有两段相似的代码,一段位于开头,一段位于while循环里,根据注释,开头这一段是“丑陋的bug修复代码”,所以主要发挥作用的应该是后者。它们都是调用了recycleByLayoutState(recycler, layoutState)来回收超出限制范围的View
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);
}
}
从布局方向的反方向回收,这里最终经过层层嵌套还是调用了Recycler#recycleViewHolderInternal,这个方法前面已经分析过了就不再赘述了。
再来看RecyclerView#dispatchLayoutStep3
private void dispatchLayoutStep3() {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.mLayoutStep = State.STEP_START;
...
mLayout.removeAndRecycleScrapInt(mRecycler);
...
}
这里调用了LayoutManager的removeAndRecycleScrapInt方法
void removeAndRecycleScrapInt(Recycler recycler) {
final int scrapCount = recycler.getScrapCount();
// Loop backward, recycler might be changed by removeDetachedView()
for (int i = scrapCount - 1; i >= 0; i--) {
final View scrap = recycler.getScrapViewAt(i);
final ViewHolder vh = getChildViewHolderInt(scrap);
if (vh.shouldIgnore()) {
continue;
}
// If the scrap view is animating, we need to cancel them first. If we cancel it
// here, ItemAnimator callback may recycle it which will cause double recycling.
// To avoid this, we mark it as not recycleable before calling the item animator.
// Since removeDetachedView calls a user API, a common mistake (ending animations on
// the view) may recycle it too, so we guard it before we call user APIs.
vh.setIsRecyclable(false);
if (vh.isTmpDetached()) {
mRecyclerView.removeDetachedView(scrap, false);
}
if (mRecyclerView.mItemAnimator != null) {
mRecyclerView.mItemAnimator.endAnimation(vh);
}
vh.setIsRecyclable(true);
// 最终也是调用recycleViewHolderInternal(holder)
recycler.quickRecycleScrapView(scrap);
}
recycler.clearScrap();
if (scrapCount > 0) {
mRecyclerView.invalidate();
}
}
这里遍历了Recycler的mAttachedScrap,挨个回收,随后清空了mAttachedScrap和mChangedScrap,确保RecyclerView中没有多余的View。
5、总结
RecyclerView的缓存复用类Recycler有四级缓存机制:
- mAttachedScrap和mChangedScrap,用于临时存储可能还会停留在RecyclerView中的ViewHolder,在dispatchLayoutStep3中多余的ViewHolder会被缓存到mCachedViews或者RecycledViewPool
- mCachedViews,缓存上限是mViewCacheMax,默认值为2,缓存最后被回收的ViewHolder
- mViewCacheExtension,开发者自定义的缓存,Recycler本身并不会往里面put数据,只检索,其他逻辑由开发者实现
- RecycledViewPool,按照分类存储ViewHolder,进入这里的ViewHolder的position等都被重置了
另外,开头的234问题,到此也有了答案~