RecyclerView的緩存機制及使用策略
RecyclerView的緩存機制是為了提高列表滾動時的性能。采用了多級緩存策略來存儲和復用視圖(View),減少視圖的創(chuàng)建和銷毀,進而減少內(nèi)存分配和GC的頻率。
緩存層級
負責回收和復用ViewHolder的類是Recycler,負責緩存的主要就是這個類的幾個成員變量。
public final class Recycler {
// 存放可見范圍內(nèi)的 ViewHolder (但是在 onLayoutChildren 的時候,會將所有 View 都會緩存到這), 從這里復用的 ViewHolder 如果 position 或者 id 對應的上,則不需要重新綁定數(shù)據(jù)。
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
// 存放可見范圍內(nèi)并且數(shù)據(jù)發(fā)生了變化的 ViewHolder,從這里復用的 ViewHolder 需要重新綁定數(shù)據(jù)。
ArrayList<ViewHolder> mChangedScrap = null;
// 存放 remove 掉的 ViewHolder,從這里復用的 ViewHolder 如果 position 或者 id 對應的上,則不需要重新綁定數(shù)據(jù)。
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
// 默認值是 2
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
// 默認值是 2
int mViewCacheMax = DEFAULT_CACHE_SIZE;
// 存放 remove 掉,并且重置了數(shù)據(jù)的 ViewHolder,從這里復用的 ViewHolder 需要重新綁定數(shù)據(jù)。 // 默認值大小是 5
RecycledViewPool mRecyclerPool;
// 自定義的緩存
private ViewCacheExtension mViewCacheExtension;
}
RecyclerView的緩存機制主要由四個部分組成,它們按照從高到低的優(yōu)先級排列:
- 「Scrap緩存(Scrap Heap)」
包括mAttachedScrap和mChangedScrap,也稱為屏內(nèi)緩存,因為它們主要用于保存屏幕內(nèi)當前可見或者即將可見的ViewHolder。
mAttachedScrap:存放的是已添加到RecyclerView但與RecyclerView臨時分離(例如在滾動或布局調(diào)整過程中)的ViewHolder。
mChangedScrap:存放的是數(shù)據(jù)已改變但尚未重新綁定數(shù)據(jù)的ViewHolder,通常用于動畫播放等場景。
- 「Cache緩存(mCachedViews)」
又稱離屏緩存,用于保存最新被移除(remove)但尚未被回收的ViewHolder。
緩存的大小是有限制的,默認最大數(shù)量為2(由DEFAULT_CACHE_SIZE定義)。
當需要展示新視圖時,會首先檢查Cache緩存中是否有可用的ViewHolder。
「ViewCacheExtension」
為開發(fā)者預留的緩存池,允許開發(fā)者自定義緩存策略,存儲更多的或特定類型的ViewHolder。
開發(fā)者可以通過實現(xiàn)ViewCacheExtension接口來擴展緩存功能。
「RecycledViewPool(mRecyclerPool)」
終極的回收緩存池,用于存放被標識為廢棄(即其他緩存池不再需要的)的ViewHolder。
這些ViewHolder已經(jīng)被抹除了數(shù)據(jù),需要重新綁定數(shù)據(jù)才能使用。
RecycledViewPool會根據(jù)不同的item類型創(chuàng)建不同的List來存儲ViewHolder。
緩存使用策略
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;
//首選該語句塊的判斷,判斷當前狀態(tài)是否為滾動狀態(tài),如果是的話,則觸發(fā) recycleByLayoutState 方法
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 分析1----回收
recycleByLayoutState(recycler, layoutState);
}
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
//分析2----復用
layoutChunk(recycler, state, layoutState, layoutChunkResult);
}
}
// 分析1----回收
// 通過一步步追蹤,我們發(fā)現(xiàn)最后調(diào)用的是 removeAndRecycleViewAt()
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
//分析1-1
removeViewAt(index);
//分析1-2
recycler.recycleView(view);
}
// 分析1-1
// 從 RecyclerView 移除一個 View
public void removeViewAt(int index) {
final View child = getChildAt(index);
if (child != null) {
mChildHelper.removeViewAt(index);
}
}
//分析1-2
// recycler.recycleView(view) 最終調(diào)用的是 recycleViewHolderInternal(holder) 進行回收 VH (ViewHolder)
void recycleViewHolderInternal(ViewHolder holder) {
if (forceRecycle || holder.isRecyclable()) {
//判斷是否滿足放進 mCachedViews
if (mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)){
// 判斷 mCachedViews 是否已滿
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
// 如果滿了就將下標為0(即最早加入的)移除,同時將其加入到 RecyclerPool 中
recycleCachedViewAt(0);
cachedViewSize--;
}
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
//如果沒有滿足上面的條件,則直接存進 RecyclerPool 中
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
}
//分析2
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//分析2-1
View view = layoutState.next(recycler);
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
//添加到 RecyclerView 上
addView(view);
} else {
addView(view, 0);
}
}
}
//分析2-1
//layoutState.next(recycler) 最后調(diào)用的是 tryGetViewHolderForPositionByDeadline() 這個方法正是 復用 核心的方法
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
// 0) If there is a changed scrap, try to find from there
// 例如:我們調(diào)用 notifyItemChanged 方法時
if (mState.isPreLayout()) {
// 如果是 changed 的 ViewHolder 那么就先從 mChangedScrap 中找
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
//如果在上面沒有找到(holder == null),那就嘗試從通過 pos 在 mAttachedScrap/ mHiddenViews / mCachedViews 中獲取
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
//如果在上面沒有找到(holder == null),那就嘗試從通過 id 在 mAttachedScrap/ mCachedViews 中獲取
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
}
if (holder == null && mViewCacheExtension != null) {
//這里是通過自定義緩存中獲取,忽略
}
//如果在上面都沒有找到(holder == null),那就嘗試在 RecycledViewPool 中獲取
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
//這里拿的是,要清空數(shù)據(jù)的
holder.resetInternal();
}
}
//如果在 Scrap / Hidden / Cache / RecycledViewPool 都沒有找到,那就只能創(chuàng)建一個了。
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
return holder;
}
- 「RecyclerView滾動時」:首先移除滑出屏幕的item,并將這些ViewHolder存入Cache緩存(mCachedViews)。如果Cache緩存已滿,則將更舊的ViewHolder存入RecycledViewPool。
- 「數(shù)據(jù)更新時」:如果屏幕內(nèi)的某個item數(shù)據(jù)發(fā)生變化,但ViewHolder仍然可見,那么這個ViewHolder會被放入mChangedScrap。當需要重新綁定數(shù)據(jù)時,會從這個緩存中取出ViewHolder。
- 「刪除item時」:被刪除的item對應的ViewHolder首先會進入Scrap緩存,然后可能會被移入Cache緩存或RecycledViewPool。
「注意」:當RecyclerView不再需要某個ViewHolder時(例如,當列表項被完全移出屏幕并且緩存已滿時),ViewHolder會被放入RecycledViewPool并最終可能被系統(tǒng)回收。
緩存機制的好處
- 「減少視圖創(chuàng)建和銷毀」:通過復用已有的ViewHolder,大大減少視圖的創(chuàng)建和銷毀次數(shù),從而節(jié)省內(nèi)存和提高性能。
- 「優(yōu)化滾動性能」:當滾動列表時,由于大部分視圖都可以從緩存中快速獲取,可以保持流暢的滾動體驗。
- 「降低GC頻率」:由于減少了視圖的創(chuàng)建和銷毀,也降低了GC的頻率,進一步提高了應用的性能。