文章目錄
概述
通過博文記錄RecyclerView的源碼學習過程有助於鞏固自己的記憶和加深整體實現機制的理解。
接《AndroidX RecyclerView總結-測量佈局》,其中LinearLayoutManager在佈局時,通過Recycler來獲取ViewHolder中的itemView進行添加和佈局。衆所周知,Recycler負責緩存ViewHolder以供複用,這裏通過追蹤源碼看看Recycler的工作機制。
源碼探究
文中源碼基於 ‘androidx.recyclerview:recyclerview:1.1.0’
Recycler使用了多個緩存集合進行多級緩存,接下來從LinearLayoutManager的佈局過程中看Recycler對ViewHolder的緩存和獲取的工作流程。
ViewHolder的存儲
先從ViewHolder存儲過程入手,看看各個緩存集合的作用。
佈局期間
在LinearLayoutManager的佈局方法onLayoutChildren中,在確定錨點之後填充佈局之前,會調用detachAndScrapAttachedViews方法進行臨時回收當前RecyclerView上attached的View對應的ViewHolder。
[LinearLayoutManager#onLayoutChildren]
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// ···
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
// 該方法中進行回收操作
detachAndScrapAttachedViews(recycler);
// ···
if (mAnchorInfo.mLayoutFromEnd) {
// ···
// 填充佈局
fill(recycler, mLayoutState, state, false);
// ···
} else {
// ···
fill(recycler, mLayoutState, state, false);
// ···
}
// ···
}
detachAndScrapAttachedViews方法中遍歷child,依次調用scrapOrRecycleView方法:
[LinearLayoutManager#scrapOrRecycleView]
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
// 獲取該view對應的ViewHolder
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
// 判斷是否ViewHolder的item數據被標記無效但還未從適配器數據集中移除。hasStableIds默認返回false
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// 將觸發detachedFromWindow和ViewGroup.removeViewAt方法
removeViewAt(index);
// 添加至mCachedViews或RecycledViewPool中緩存
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 將爲index對應的viewHolder添加FLAG_TMP_DETACHED標記,觸發ViewGroup.detachViewFromParent方法
detachViewAt(index);
// 添加至mAttachedScrap或mChangedScrap中緩存
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
當重新設置Adapter或調用Adapter的notifyDataSetChanged方法會給ViewHolder標記FLAG_INVALID,需要完全重新綁定View。當移除Adapter中數據集的某個item數據時會給對應的ViewHolder標記FLAG_REMOVED,但它綁定的View可能仍然需要保留以用於item動畫。同時滿足以上情況時調用recycleViewHolderInternal方法進行緩存,否則調用scrapView緩存。
這裏的scrap和recycle是兩種不同行爲。scrap表示着View仍然在RecyclerView上,只是臨時detach,稍後會再attach回來。recycle意味着View將移出RecyclerView,緩存ViewHolder實例,可能不用重新綁定View,但是對應的索引位置將不一致。
mCachedViews
recycleViewHolderInternal方法主要是對要移出RecyclerView的ViewHolder,或是item數據徹底無效或徹底移除的ViewHolder進行緩存。當RecyclerView上下滑動或item消失動畫結束或徹底移除適配器數據集中對應的item等情況都會調用該方法進行回收。
[RecyclerView#recycleViewHolderInternal]
void recycleViewHolderInternal(ViewHolder holder) {
// 省略異常檢查部分 ···
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
@SuppressWarnings("unchecked")
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
// ···
// 判斷是否強制回收或ViewHolder設置可回收,默認爲true
if (forceRecycle || holder.isRecyclable()) {
// mViewCacheMax默認爲2,判斷ViewHolder是否需要重新綁定
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();
// 判斷mCachedViews容量是否已滿
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
// 移除第一個添加的ViewHolder,並通過addViewHolderToRecycledViewPool方法將其轉移到RecycledViewPool中
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中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 若不滿足添加進mCachedViews的條件,則添加進RecycledViewPool中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
// DEBUG···
}
// ···
}
這裏將無需重新綁定View的ViewHolder保存在mCachedViews中,若mCachedViews容量不足(默認上限2),則將最早添加的轉移到RecycledViewPool中。
若不滿足添加進mCachedViews的條件,則將ViewHolder添加進RecycledViewPool。
RecycledViewPool
在addViewHolderToRecycledViewPool方法中通過getRecycledViewPool方法獲取RecycledViewPool實例:
[RecyclerView#getRecycledViewPool]
RecycledViewPool getRecycledViewPool() {
if (mRecyclerPool == null) {
mRecyclerPool = new RecycledViewPool();
}
return mRecyclerPool;
}
若mRecyclerPool已經存在,則直接返回。mRecyclerPool也可通過RecyclerView.setRecycledViewPool方法傳入一個實例獲得,從而支持多個RecyclerView共用一個RecycledViewPool。
這裏先看一下RecycledViewPool的主要結構:
[RecycledViewPool]
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;
// ···
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
// ···
}
RecycledViewPool的mScrap成員以ViewHolder的viewType爲key,ScrapData爲value。ScrapData中持有一個ViewHolder集合,集合的容量是5。當添加ViewHolder時,需要先取出對應viewType的ScrapData。
(可以理解爲類似Map/<Integer, list//>的集合)
在取得RecycledViewPool實例後,調用它的putRecycledView方法進行添加:
[RecycledViewPool#putRecycledView]
public void putRecycledView(ViewHolder scrap) {
// 獲取該ViewHolder的viewType
final int viewType = scrap.getItemViewType();
// 獲取viewType對應的ViewHolder集合
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");
}
// 重置ViewHolder中的數據
scrap.resetInternal();
// 添加集合保存
scrapHeap.add(scrap);
}
RecycledViewPool的作用是一個對象緩存池,避免頻繁創建ViewHolder,但是ViewHolder仍然需要重新進行View綁定。
mAttachedScrap、mChangedScrap
回到scrapOrRecycleView方法中,正常佈局情況下會進入Recycler的scrapView方法:
[Recycler#scrapView]
void scrapView(View view) {
// 獲取該view對應的ViewHolder
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和mChangedScrap都用於緩存RecyclerView上臨時detach的ViewHolder。區別是mAttachedScrap保存的是沒有變化的ViewHolder,mChangedScrap保存的是有變化的,例如調用了Adapter.notifyItemRangeChanged方法。
滾動期間
RecyclerView觸發滾動時會調用LinearLayoutManager的scrollBy方法:
[LinearLayoutManager#scrollBy]
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
// ···
mLayoutState.mRecycle = true;
// ···
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
// ···
}
該方法中先將mRecycle標記爲true(默認爲false,其他場景也都爲false),之後通過fill方法進行填充。
在fill方法中會判斷是否存在滾動,並對移除屏幕的View進行回收緩存:
[LinearLayoutManager#fill]
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// ···
// 判斷是否滾動
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);
}
// ···
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);
}
}
// ···
}
在開始循環填充View前,先檢查上下滑動的回收,在每填充一個View後也進行一次檢查回收。
recycleByLayoutState方法中會判斷mRecycle變量是否爲false,該變量默認爲true,但在前面的scrollBy中會設置爲false。接着根據佈局方向從頂部或底部回收ViewHolder,通過for循環逐個回收離開屏幕的View,回收的代碼在removeAndRecycleViewAt方法中。
進入removeAndRecycleViewAt方法:
[LinearLayoutManager#removeAndRecycleViewAt]
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
// 從ViewGroup移除View
removeViewAt(index);
// 使用Recycler進行回收
recycler.recycleView(view);
}
在recycleView方法中會進行一些回調和清理工作,並調用recycleViewHolderInternal方法回收ViewHolder,保存進mCachedViews或RecycledViewPool中。
ViewHolder的獲取
接着看獲取緩存的工作流程,看看各緩存集合的讀取優先級。LinearLayoutManager在layoutChunk方法中進行單個View的添加和佈局,該方法中首先通過LayoutState的next方法獲取View,而next方法中又調用Recycler的getViewForPosition方法並傳入當前適配器item數據的索引:
[Recycler#getViewForPosition]
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
通過tryGetViewHolderForPositionByDeadline獲取到ViewHolder後,取裏面的itemView返回。
tryGetViewHolderForPositionByDeadline方法較長,這裏分成幾部分看:
從緩存集合中查找
[Recycler#tryGetViewHolderForPositionByDeadline]
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
if (mState.isPreLayout()) {
// 如果是預佈局則從mChangedScrap中查找
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
// 先從mAttachedScrap中查找,再從ChildHelper的mHiddenViews(保留用於動畫的View)
// 中查找,再從mCachedViews中查找
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
// 檢查ViewHolder是否無效
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();
}
// 重新回收該ViewHolder
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
// ···
}
優先mChangedScrap或mAttachedScrap中查找,找不到再從mCachedViews中查找。
[Recycler#tryGetViewHolderForPositionByDeadline]
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
// ···
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
// hasStableIds默認返回false
if (mAdapter.hasStableIds()) {
// 先從mAttachedScrap中查找,再從mCachedViews中查找
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.
// 從ViewCacheExtension中查找,
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;
}
// 觸發Adapter的onCreateViewHolder回調創建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");
}
}
}
// ···
}
緩存集合查找優先級:mAttachedScrap->mCachedViews->ViewCacheExtension->RecycledViewPool
- ViewCacheExtension說明
ViewCacheExtension抽象類需要開發者繼承,實現getViewForPositionAndType方法,完成具體的緩存策略。RecyclerView.mViewCacheExtension默認爲null,通過RecyclerView.setViewCacheExtension方法設置。
新建ViewHolder
當從以上緩存集合中都沒有找到可用的ViewHolder後,會Adapter.createViewHolder方法進行創建。
[Adapter#createViewHolder]
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
try {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
// onCreateViewHolder由開發者實現,返回具體的ViewHolder
final VH holder = onCreateViewHolder(parent, viewType);
if (holder.itemView.getParent() != null) {
throw new IllegalStateException("ViewHolder views must not be attached when"
+ " created. Ensure that you are not passing 'true' to the attachToRoot"
+ " parameter of LayoutInflater.inflate(..., boolean attachToRoot)");
}
holder.mItemViewType = viewType;
return holder;
} finally {
TraceCompat.endSection();
}
}
觸發了onCreateViewHolder回調方法返回ViewHolder。
綁定View
[Recycler#tryGetViewHolderForPositionByDeadline]
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
// ···
boolean bound = false;
// 判斷是否需要進行View綁定操作
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);
// 綁定View
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
// 生成RecyclerView.LayoutParams,並使之和ViewHolder互相持有引用
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未綁定或需要重新綁定,則調用tryBindViewHolderByDeadline方法進行綁定:
[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;
}
// 調用Adapter的bindViewHolder方法進行綁定
mAdapter.bindViewHolder(holder, offsetPosition);
long endBindNs = getNanoTime();
mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
attachAccessibilityDelegateOnBind(holder);
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
return true;
}
該方法中調用Adapter的bindViewHolder方法,bindViewHolder又調用Adapter的onBindViewHolder回調方法,又開發者實現具體的綁定邏輯。
總結
在Recycler中的緩存集合:
-
mAttachedScrap、mChangedScrap
填充佈局前緩存當前RecyclerView上的ViewHolder,ViewHolder會短暫detach,不會remove。mAttachedScrap和mChangedScrap的區別是mChangedScrap保存需要局部刷新的ViewHolder,例如Adapter.notifyItemRangeChanged指定範圍之間的。查找優先級最高。 -
mCachedViews
RecyclerView上下滑動或Adapter數據集變更或item移出等導致ViewHolder索引位置失效或item內容變化等數據無效的情況下緩存ViewHolder。容量上限默認是2。優先級次高。 -
RecycledViewPool
當往mCachedViews添加但mCachedViews超限時,會將mCachedViews裏面最早添加的ViewHolder轉存到RecycledViewPool中,同時重置該ViewHolder的數據。RecycledViewPool中根據viewType區分保存ViewHolder,每種viewType的默認存儲上限是5。可以多個RecyclerView共用一個RecycledViewPool。優先級最低。 -
ViewCacheExtension
默認爲空,需要開發者自行繼承實現,只有獲取緩存時調用,返回緩存View。優先級介於mCachedViews和RecycledViewPool之間。
當沒有可用緩存時,會通過Adapter的onCreateViewHolder回調返回開發者創建的ViewHolder。在獲取到ViewHolder後,判斷該ViewHolder是否未綁定View(新創建的)或需要重新綁定View(數據無效、數據重置),若要綁定的話,通過Adapter的onBindViewHolder回調執行開發者實現的具體綁定邏輯。