寫在前面:今天2021年2月12日,農(nóng)歷正月初一,2021年春節(jié)。一個人在上海沒有回家。從家里下了幾個水餃吃了,然后來公司了,學會習,下午再跑個5公里。拍張照紀念一下。

進入正題
上篇文章:RecyclerView源碼分析之一
源碼版本:androidx1.1.0
本文要旨:RecyclerView的回收和復用機制。
我們就以RecyclerView最簡單的使用方式為例進行分析。使用線性布局,方向為豎直方向,布局從上到下。
先引用這篇文章中RecyclerView 源碼分析(三) - RecyclerView的緩存機制對RecyclerView緩存的總結(jié),感覺非常清晰非常好。
| 緩存級別 | 實際變量 | 含義 |
|---|---|---|
| 一級緩存 |
mAttachedScrap和mChangedScrap
|
優(yōu)先級最高的緩存,RecyclerView在獲取ViewHolder時,優(yōu)先會到這兩個緩存來找。其中mAttachedScrap存儲的是當前還在屏幕中的ViewHolder,mChangedScrap存儲的是數(shù)據(jù)被更新的ViewHolder,比如說調(diào)用了Adapter的notifyItemChanged方法。 |
| 二級緩存 | mCachedViews |
默認大小為2,在滾動的時候會存儲一些ViewHolder。 |
| 三級緩存 | ViewCacheExtension |
這個是自定義緩存,一般用不到。 |
| 四級緩存 | RecyclerViewPool |
根據(jù)ViewType來緩存ViewHolder,每個ViewType的數(shù)組大小為5,可以動態(tài)的改變。 |
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
//根據(jù)postion來尋找可復用的ViewHolder
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
//自定義的緩存擴展,一般不會定義吧。
private ViewCacheExtension mViewCacheExtension;
//根據(jù)type來尋找來尋找可復用的ViewHolder
RecycledViewPool mRecyclerPool;
static final int DEFAULT_CACHE_SIZE = 2;
}
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
}
還有一篇文章中,說的感覺更清晰易懂。
1、AttachedScrap: 存放可見、不需要重新綁定的ViewHolder
2、CachedViews: 存放不可見、不需要重新綁定的ViewHoler
3、ViewCacheExtension: 自定義緩存(存放不可見、不需要重新綁定)
4、RecyclerPool: 存放不可見、需要重新綁定的ViewHolder
【Android】自定義無限循環(huán)的LayoutManager
RecyclerView的回收
在布局的時候的回收
在RecyclerView的布局的第二步dispatchLayoutStep2中,會調(diào)用LinearLayoutManager的onLayoutChildren方法。
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//...
//注釋1處,如果當前存在attach到RecyclerView的View,則臨時detach,后面再復用。
detachAndScrapAttachedViews(recycler);
//...
}
注釋1處,如果當前存在attach到RecyclerView的View,則臨時detach,后面再復用。
RecyclerView.LayoutManager的detachAndScrapAttachedViews方法。
/**
* 臨時detach和廢棄當前attached的子Views。Views會被加入指定的Recycler。Recycler可能會在其他先前被回收的Views之前優(yōu)先使用`scrap views`。
*
* @param recycler Recycler to scrap views into
*/
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
//調(diào)用scrapOrRecycleView方法
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
return;
}
//如果ViewHolder的數(shù)據(jù)無效了 && ViewHolder對應的數(shù)據(jù)沒有被從data set中移除 && 并且沒有固定的id
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
//注釋1處,移除View
removeViewAt(index);
//注釋2處,回收ViewHolder
recycler.recycleViewHolderInternal(viewHolder);
} else {
//注釋3處,detachView
detachViewAt(index);
//注釋4處,回收View
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
注釋1處,移除View,這里會真正把子View從RecyclerView中移除。
注釋2處,回收ViewHolder,正常滾動的時候也會調(diào)用Recycler的recycleViewHolderInternal方法,后面分析滾動回收的時候再看。
注釋3處,臨時將一個View從當前窗口detach,然后復用的時候重新attach一下就行,效率非常高。
Recycler的scrapView方法。
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
/*
* 1. 如果ViewHolder對應的數(shù)據(jù)從data set中移除了,或者ViewHolder對應的數(shù)據(jù)無效了,需要重新綁定新的數(shù)據(jù)。
* 2. 或者ViewHolder對應的數(shù)據(jù)沒有更新
* 3. 或者canReuseUpdatedViewHolder(holder)方法為true,這方法默認是true
*/
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
//如果ViewHolder的數(shù)據(jù)無效了 && ViewHolder對應的數(shù)據(jù)沒有被從data set中移除 && 并且沒有固定的id
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());
}
//調(diào)用setScrapContainer方法,第二個參數(shù)是isChangeScrap的意思,這里為false
holder.setScrapContainer(this, false);
//將ViewHolder回收到mAttachedScrap
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
//調(diào)用setScrapContainer方法,第二個參數(shù)是isChangeScrap的意思,這里為true
holder.setScrapContainer(this, true);
//將ViewHolder回收到mChangedScrap
mChangedScrap.add(holder);
}
}
這里會根據(jù)ViewHolder的狀態(tài),決定將其加入到mAttachedScrap還是mChangedScrap。mAttachedScrap還是mChangedScrap的區(qū)別引用這篇文章中的敘述RecyclerView 源碼分析(三) - RecyclerView的緩存機制
這是優(yōu)先級最高的緩存,RecyclerView在獲取ViewHolder時,優(yōu)先會到這兩個緩存來找。其中mAttachedScrap存儲的是當前還在屏幕中的ViewHolder,mChangedScrap存儲的是數(shù)據(jù)被更新的ViewHolder,比如說調(diào)用了Adapter的notifyItemChanged方法。
在滾動時候的回收
在RecyclerView滾動的時候,會調(diào)用LinearLayoutManager的fill方法。fill方法內(nèi)部會發(fā)生View的回收和復用。
/**
* @param recycler 當前關聯(lián)到RecyclerView的recycler。
* @param layoutState 該如何填充可用空間的配置信息。
* @param state Context passed by the RecyclerView to control scroll steps.
* @param stopOnFocusable 如果為true的話,遇到第一個可獲取焦點的View則停止填充。
* @return 返回添加的像素。
*/
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;
//...
//注釋0處,如果layoutState.mScrollingOffset不為SCROLLING_OFFSET_NaN的話,調(diào)用recycleByLayoutState方法,從這個方法名,我們可以看出來,這是一個回收View的方法。
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
//...
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
//注釋1處,獲取并添加子View,然后測量、布局子View并將分割線考慮在內(nèi)。
layoutChunk(recycler, state, layoutState, layoutChunkResult);
//條件滿足的話,跳出循環(huán)
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
//注釋2處,這里也會判斷是否要調(diào)用recycleByLayoutState方法。
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;
}
}
return start - layoutState.mAvailable;
}
注釋0處和注釋2處,如果layoutState.mScrollingOffset不為SCROLLING_OFFSET_NaN的話,調(diào)用recycleByLayoutState方法,從這個方法名可以看出來,這是一個回收View的方法。接下來我們看看其中的細節(jié)。
LinearLayoutManager的recycleByLayoutState方法。
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) {
//注釋1處
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
//注釋2處
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
}
注釋1處,以默認豎直方向的LinearLayoutManager來說,就是手指從上向下滑動的時候,回收從下面滑出屏幕的View。
注釋2處,以默認豎直方向的LinearLayoutManager來說,就是手指從下向上滑動的時候,回收從上面滑出屏幕的View。
LinearLayoutManager的recycleViewsFromEnd方法。
private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
final int childCount = getChildCount();
if (scrollingOffset < 0) {
return;
}
final int limit = mOrientationHelper.getEnd() - scrollingOffset + noRecycleSpace;
if (mShouldReverseLayout) {
//...
} else {
//從后向前遍歷
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedStart(child) < limit
|| mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
//注釋1處,遇到第一個View的top坐標小于limit就停止。然后調(diào)用recycleChildren方法,回收從childCount - 1到i之間的所有View。
recycleChildren(recycler, childCount - 1, i);
return;
}
}
}
}
注釋1處,遇到第一個View的top坐標小于limit就停止。然后調(diào)用recycleChildren方法,回收從childCount - 1到i之間的所有View。
LinearLayoutManager的recycleViewsFromStart方法。
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
if (scrollingOffset < 0) {
return;
}
// ignore padding, ViewGroup may not clip children.
final int limit = scrollingOffset - noRecycleSpace;
final int childCount = getChildCount();
if (mShouldReverseLayout) {
//...
} else {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
//注釋1處,遇到第一個View的bottom坐標大于limit就停止。然后調(diào)用recycleChildren方法,回收從0到i之間的所有View。
recycleChildren(recycler, 0, i);
return;
}
}
}
}
注釋1處,遇到第一個View的bottom坐標大于limit就停止。然后調(diào)用recycleChildren方法,回收從0到i之間的所有View。
接下來我們看看recycleChildren方法。
LinearLayoutManager的recycleChildren方法。
/**
* 回收指定索引之間的子View。
*
* @param startIndex 包括
* @param endIndex 不包括
*/
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
if (startIndex == endIndex) {
return;
}
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);
}
}
}
調(diào)用父類RecyclerView.LayoutManager的removeAndRecycleViewAt方法。
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
//注釋1處,調(diào)用RecyclerView.LayoutManager的removeViewAt方法移除View。
removeViewAt(index);
//注釋2處,回收View。
recycler.recycleView(view);
}
RecyclerView.LayoutManager的removeAndRecycleViewAt方法。
public void removeViewAt(int index) {
final View child = getChildAt(index);
if (child != null) {
//這里調(diào)用RecyclerView的mChildHelper進行移除。
mChildHelper.removeViewAt(index);
}
}
在RecyclerView的initChildrenHelper方法中,初始化了mChildHelper,并傳入了一個ChildHelper.Callback對象。最終就是調(diào)用這個ChildHelper.Callback對象的removeViewAt方法來移除的。
@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.
child.clearAnimation();
}
//注釋1處,RecyclerView自身調(diào)用ViewGroup的removeViewAt方法來移除子View。
RecyclerView.this.removeViewAt(index);
}
注釋1處,RecyclerView自身調(diào)用ViewGroup的removeViewAt方法來移除子View。
然后我們回到RecyclerView.LayoutManager的removeAndRecycleViewAt方法的注釋2處,回收移除掉的View。
recycler.recycleView(view);
Recycler的recycleView方法。
public void recycleView(@NonNull View view) {
// This public recycle method tries to make view recycle-able since layout manager
// intended to recycle this view (e.g. even if it is in scrap or change cache)
//獲取ViewHolder
ViewHolder holder = getChildViewHolderInt(view);
if (holder.isTmpDetached()) {
//清除FLAG_TMP_DETACHED臨時分離的標記
removeDetachedView(view, false);
}
//注釋1處,將ViewHolder從mChangedScrap或者mAttachedScrap中移除。
if (holder.isScrap()) {
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
//注釋2處,調(diào)用recycleViewHolderInternal方法。
recycleViewHolderInternal(holder);
if (mItemAnimator != null && !holder.isRecyclable()) {
mItemAnimator.endAnimation(holder);
}
}
注釋1處,如果ViewHolder在mChangedScrap中或者mAttachedScrap中,則將ViewHolder從mChangedScrap或者mAttachedScrap中移除。也就是說滑出屏幕的ViewHolder不會緩存在mChangedScrap中或者mAttachedScrap中。
注釋2處,調(diào)用recycleViewHolderInternal方法。
Recycler的recycleViewHolderInternal方法。
void recycleViewHolderInternal(ViewHolder holder) {
//這里判斷了在mChangedScrap中或者mAttachedScrap中的ViewHolder不會被回收,沒有被移除的子View對應的ViewHolder也不能被回收。
if (holder.isScrap() || holder.itemView.getParent() != null) {
throw new IllegalArgumentException(
"Scrapped or attached views may not be recycled. isScrap:"
+ holder.isScrap() + " isAttached:"
+ (holder.itemView.getParent() != null) + exceptionLabel());
}
//臨時從屏幕上detach的ViewHolder也不能被回收。
if (holder.isTmpDetached()) {
throw new IllegalArgumentException("Tmp detached view should be removed "
+ "from RecyclerView before it can be recycled: " + holder
+ exceptionLabel());
}
//不需要回收的ViewHolder
if (holder.shouldIgnore()) {
throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
+ " should first call stopIgnoringView(view) before calling recycle."
+ exceptionLabel());
}
//是否阻止回收
final boolean transientStatePreventsRecycling = holder.doesTransientStatePreventRecycling();
//是否強制回收
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()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
//注釋1處,如果mCachedView緩存已達上限,從mCachedViews中移除最老的ViewHolder到RecyclerViewPool中
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處,將要回收的ViewHolder加入mCachedViews
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
//注釋3處,沒有成功緩存到mCachedViews,則加入到RecycledViewPool中。
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
// NOTE: A view can fail to be recycled when it is scrolled off while an animation
// runs. In this case, the item is eventually recycled by
// ItemAnimatorRestoreListener#onAnimationFinished.
// TODO: consider cancelling an animation when an item is removed scrollBy,
// to return it to the pool faster
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.
//跟動畫相關的ViewHolder也從mViewInfoStore移除。
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
注釋1處,如果mCachedView緩存已達上限,從mCachedViews中移除最老的ViewHolder到RecyclerViewPool中。
Recycler的recycleCachedViewAt方法。
void recycleCachedViewAt(int cachedViewIndex) {
//從mCachedViews中獲取ViewHolder
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
//加入到RecycledViewPool
addViewHolderToRecycledViewPool(viewHolder, true);
//mCachedViews移除對應位置上的ViewHolder
mCachedViews.remove(cachedViewIndex);
}
注釋2處,先將要回收的ViewHolder加入mCachedViews中。
注釋3處,沒有成功緩存到mCachedViews,則加入到RecycledViewPool中。
我們接下來看看Recycler的addViewHolderToRecycledViewPool方法。
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);
View itemView = holder.itemView;
//...
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
holder.mOwnerRecyclerView = null;
//注釋1處,調(diào)用RecycledViewPool的putRecycledView方法。
getRecycledViewPool().putRecycledView(holder);
}
RecycledViewPool的putRecycledView方法。
/**
* 將廢棄的ViewHolder加入到緩存池。
* <p>
* 如果ViewHolder對應的ViewType類型的緩存池已經(jīng)滿了,就直接將ViewHolder丟棄。
*
* @param scrap ViewHolder to be added to the pool.
*/
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
//根據(jù)viewType獲取緩存池,就是一個ArrayList<ViewHolder>
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
//緩存池已經(jīng)滿了,不回收,直接return。
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
//ViewHolder做一下清除操作。
scrap.resetInternal();
//緩存ViewHolder
scrapHeap.add(scrap);
}
RecyclerView的復用
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//注釋1處,獲取子View,可能是從緩存中或者新創(chuàng)建的View。后面分析緩存相關的點的時候再看。
View view = layoutState.next(recycler);
if (view == null) {
//注釋2處,如果獲取到的子View為null,將LayoutChunkResult的mFinished置為true,用于跳出循環(huán)然后直接return。
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
//注釋3處
addView(view);
} else {
//注釋4處
addView(view, 0);
}
}
//...
}
上篇文章我們分析了layoutChunk方法。在注釋1處,獲取子View,可能是從緩存中或者新創(chuàng)建的View。我們現(xiàn)在就看一看其中細節(jié)。
View view = layoutState.next(recycler);
LayoutState的next方法。
View next(RecyclerView.Recycler recycler) {
//如果需要布局特殊的View是,可以將mScrapList賦值為不為null的值,然后就會從mScrapList獲取View,這里可以忽略
if (mScrapList != null) {
return nextViewFromScrapList();
}
//注釋1處,調(diào)用Recycler的getViewForPosition方法獲取View
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
注釋1處,正常調(diào)用Recycler的getViewForPosition方法獲取View。RecyclerView.Recycler就是處理RecyclerView的緩存復用相關邏輯的類。
@NonNull
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
tryGetViewHolderForPositionByDeadline方法里面的邏輯就是先從緩存里面取ViewHolder進行復用,如果沒有可復用的ViewHolder,則進行創(chuàng)建。
@Nullable
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
//注釋0處,從mChangedScrap中獲取ViewHolder,注意只有LinearLayoutManager沒有開啟自動測量的情況下,mState.isPreLayout()才可能是是true,我們這里就忽略mState.isPreLayout()為true的情況。
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
//注釋1處,從mAttachedScrap、mHiddenViews、mCachedViews中獲取ViewHolder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
//是否可以在該位置上使用holder,
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;
}
}
}
//holder為null,繼續(xù)進行查找
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());
}
//獲取ViewType
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
//注釋2處,通過stable ids 從mAttachedScrap、mCachedViews中獲取ViewHolder。
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
//注釋3處,從我們自定義的緩存擴展mViewCacheExtension中獲取ViewHolder
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);
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");
}
//注釋4處,從RecycledViewPool中獲取ViewHolder
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;
}
//注釋5處,從適配器中創(chuàng)建一個新的ViewHolder
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");
}
}
}
// 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);
//注釋6處,綁定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;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
//最終返回ViewHolder對象。
return holder;
}
注釋0處,從mChangedScrap中獲取ViewHolder。
if (mState.isPreLayout()) {//開啟了預布局
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
注意只有LinearLayoutManager沒有開啟自動測量的情況下,mState.isPreLayout()才可能是是true,并不是常規(guī)行為,我們這里就忽略這種情況。
注釋1處,從mAttachedScrap、mHiddenViews、mCachedViews中獲取ViewHolder。傳入的dryRun為false。這個是用來標記找到一個ViewHolder以后,是否從緩存它的數(shù)據(jù)集中移除掉。
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
RecyclerView.Recycler的getScrapOrHiddenOrCachedHolderForPosition方法。
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
//注釋1處,先從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) {
//注釋2處,從mHiddenViews中查找View,然后根據(jù)View獲取ViewHolder
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);
//將獲取到的View從mHiddenViews移除。
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);
//注釋3處,將View對應的ViewHolder加入到mAttachedScrap或者mChangedScrap中
scrapView(view);
//添加標記位,多了一個FLAG_BOUNCED_FROM_HIDDEN_LIST的標記位
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
//注釋4處,從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) {
//找到ViewHolder后,從mCachedViews中移除。
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
return holder;
}
}
return null;
}
注釋1處,先從mAttachedScrap查找。
注釋2處,從mHiddenViews中查找View,然后根據(jù)View獲取ViewHolder。獲取到的View會從mHiddenViews移除,然后注釋3處,將View對應的ViewHolder加入到mAttachedScrap或者mChangedScrap中。
這里順便提一下,mHiddenViews存儲的是在動畫過程中需要隱藏的Views。
注釋4處,從mCachedViews中獲取。
然后我們回到RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注釋2處。通過stable ids從mAttachedScrap、mCachedViews中獲取ViewHolder。
RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注釋3處,從我們自定義的緩存擴展mViewCacheExtension中獲取ViewHolder,這個自定義的ViewCacheExtension就先忽略了。
(你自定義過ViewCacheExtension嗎?,沒有。你自定義過?我也沒有?正常人誰自定義ViewCacheExtension呀。是,自定義ViewCacheExtension的叫正常人嗎?哈哈,純屬搞笑。)
RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注釋4處,從RecycledViewPool中獲取ViewHolder。
holder = getRecycledViewPool().getRecycledView(type);
RecycledView.RecycledViewPool類,這里提一下,RecycledViewPool可以用來在多個RecyclerView之間來復用View。
/**
* RecycledViewPool可以讓你在多個RecyclerViews之間復用Views。 lets you share Views between multiple RecyclerViews.
* <p>
* 如果你想在多個RecyclerView之間復用Views,可以創(chuàng)建一個RecycledViewPool實例,然后調(diào)用 RecyclerView的 setRecycledViewPool(RecycledViewPool) 方法設置。
* <p>
* 如果你不提供RecycledViewPool實例,RecyclerView會自動創(chuàng)建一個。
*/
public static class RecycledViewPool {
//每個viewType類型的ViewHolder默認緩存5個
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緩存不ViewType類型的ViewHolder,SparseArray的key就是ViewType類
SparseArray<ScrapData> mScrap = new SparseArray<>();
@Nullable
public ViewHolder getRecycledView(int viewType) {
//注釋1處,先根據(jù)ViewType類獲取ScrapData,然后從ScrapData獲取ViewHolder。
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;
}
//...
}
注釋1處,從RecycledViewPool中獲取ViewHolder,先根據(jù)ViewType類獲取ScrapData,然后從ScrapData獲取ViewHolder。
RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注釋5處,無法從緩存中獲取ViewHolder,就使用適配器中創(chuàng)建一個新的ViewHolder。
RecyclerView.Recycler的tryGetViewHolderForPositionByDeadline方法的注釋6處,綁定ViewHolder
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
Recycler的tryBindViewHolderByDeadline方法。
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,內(nèi)部最終會調(diào)用我們平常寫的onBindViewHolder方法。
mAdapter.bindViewHolder(holder, offsetPosition);
long endBindNs = getNanoTime();
mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
attachAccessibilityDelegateOnBind(holder);
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
return true;
}
結(jié)尾:關于RecyclerView的動畫相關的內(nèi)容,并沒有進行分析。主要還是關注RecyclerView的回收和復用的大致邏輯。
參考鏈接: