源碼的世界及其復(fù)雜,要是每一步都去深究,很容易迷失在里面,這里將RecyclerView的緩存機(jī)制抽出來(lái)重點(diǎn)分析,結(jié)合圖文的方式,希望可以給您帶來(lái)幫助!
RecyclerView的緩存機(jī)制猶如一個(gè)強(qiáng)大的引擎,為RecyclerView的暢滑運(yùn)行提供了強(qiáng)有力的保障;Android的大部分視圖都是列表形式的,那么RecyclerView的出現(xiàn)無(wú)疑大大的提升了開(kāi)發(fā)效率;那么RecyclerView的緩存究竟是如何工作的呢,那就讓我們來(lái)揭開(kāi)謎底吧!
RecyclerView的緩存機(jī)制就是依附于Recycler這個(gè)類來(lái)實(shí)現(xiàn)的,讓我們先來(lái)看一下這個(gè)類的成員變量:
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
}
Recycler分析:
Recycler的成員變量總共有五個(gè)集合,分為兩部分,具體請(qǐng)看下面介紹;
1,Scrap部分:
(1)mAttachedScrap:存儲(chǔ)的是當(dāng)前還在屏幕中的ViewHolder;
(2)mChangedScrap:存儲(chǔ)的是數(shù)據(jù)被更新的ViewHolder,比如說(shuō)調(diào)用了Adapter的notifyItemChanged方法;
2,Cache部分:
(1)mCachedViews:默認(rèn)大小為2,通常用來(lái)存儲(chǔ)預(yù)取的ViewHolder,同時(shí)在回收ViewHolder時(shí),也會(huì)可能存儲(chǔ)一部分的ViewHolder,這部分的ViewHolder通常來(lái)說(shuō),意義跟一級(jí)緩存差不多;
(2)mRecyclerPool:根據(jù)ViewType來(lái)緩存ViewHolder,每個(gè)ViewType的數(shù)組大小為5,可以動(dòng)態(tài)的改變;
(3)mViewCacheExtension:自定義緩存;
RecyclerView總共有4級(jí)緩存:
第一級(jí)緩存:Scrap部分;
第二級(jí)緩存:mCachedViews;
第三級(jí)緩存:mViewCacheExtension;
第四級(jí)緩存:mRecyclerPool;
那么具體是怎么實(shí)現(xiàn)的呢,讓我們根據(jù)源碼來(lái)分析吧;
首頁(yè)我們先看看ViewHolder的獲取流程;
2,具體流程分析:
1,ViewHolder獲取流程:
首先先看流程圖:

看完流程圖,那么接下來(lái)具體分析一下源碼是怎么操作的;
我在上一篇博客里面分析了RecyclerView的繪制流程,里面提到了獲取ViewHolder 的方法,也就是layoutChunk方法里面的next(recycler),讓我們看一下源碼里面寫了啥?
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
這里最終調(diào)用的是tryGetViewHolderForPositionByDeadline();
繼續(xù)分析tryGetViewHolderForPositionByDeadline()方法:
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
// 第一步
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 第二步
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
...
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
...
// 第三步
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
...
}
// 第四步
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 = mAdapter.createViewHolder(RecyclerView.this, type);
}
...
return holder;
}
(1)第一步:
首先,先判斷是否是預(yù)布局,也就是dispatchLayoutStep1(),這個(gè)方法在上一篇博客也已經(jīng)分析過(guò)了,具體可以點(diǎn)擊查看;
判斷如果是的話則從getChangedScrapViewForPosition()方法去獲取緩存的ViewHolder,
getChangedScrapViewForPosition()方法分析:
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;
}
// find by 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;
}
}
// find by 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;
}
這里做的操作就是從mChangedScrap里通過(guò)ItemID來(lái)獲取緩存的ViewHolder;
并給這個(gè)ViewHolder添加標(biāo)記位(ViewHolder.FLAG_RETURNED_FROM_SCRAP),表示是從Scrap這個(gè)緩存里面獲取的;
(2)第二步:
第二步通過(guò)getScrapOrHiddenOrCachedHolderForPosition()方法來(lái)獲取緩存,讓我們看源碼繼續(xù)分析:
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
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;
}
}
if (!dryRun) {
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);
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
// Search in our first-level recycled view cache.
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) {
if (!dryRun) {
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
return holder;
}
}
return null;
}
通過(guò)上面源碼分析,這里是通過(guò)position先從mChangedScrap這個(gè)集合里面取緩存,如果取得到則給這個(gè)ViewHolder添加標(biāo)記位(ViewHolder.FLAG_RETURNED_FROM_SCRAP),表示是從Scrap這個(gè)緩存里面獲取的;mChildHelper里的mHiddenViews是與動(dòng)畫相關(guān)的緩存獲取,這里就不進(jìn)行分析了;那么如果從mChangedScrap獲取不到ViewHolder,下面就會(huì)從mCachedViews里面獲取緩存;
validateViewHolderForOffsetPosition()這個(gè)方法是用來(lái)判斷ViewHoler是否有效,如果無(wú)效了,則進(jìn)行回收,具體操作在recycleViewHolderInternal(holder)這個(gè)方法里,后面會(huì)進(jìn)行詳細(xì)分析;
(3)第三步:
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
這里通過(guò)判斷hasStableIds是否為true,如果為true則通過(guò)getScrapOrCachedViewForId()方法來(lái)獲取緩存,這里是先從mChangedScrap里獲取緩存,如果獲取不到則從mCachedViews里面獲取緩存;和第二步類似這里就不過(guò)多分析了;
(4)第四步:
這一步通過(guò)mViewCacheExtension來(lái)獲取緩存,這個(gè)是自定義緩存,用到場(chǎng)景較少,也不過(guò)多分析了;
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
int type);
這里是抽象方法,具體獲取邏輯由子類實(shí)現(xiàn);
(5)第五步:
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;
}
這里是通過(guò)RecycledViewPool里的getRecycledView方法來(lái)獲取緩存,這里的mScrap是Android自定義的集合SparseArray,和map一樣,只是效率會(huì)更高效一些;這里通過(guò)mScrap獲取scrapHeap的集合,然后獲取該集合的最后一個(gè)元素;
(6)第六步:
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
try {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
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();
}
}
當(dāng)上面的幾步都獲取不到ViewHolder時(shí),則通過(guò)調(diào)用Adapter的onCreateViewHolder()方法來(lái)創(chuàng)建一個(gè)ViewHolder并返回給RecyclerView;
那么到這里ViewHolder的獲取就分析完畢了;
2,ViewHolder的回收流程:
先來(lái)看一張?jiān)敿?xì)的流程圖:

這里把復(fù)雜的源碼通過(guò)流程圖展示出來(lái),源碼的細(xì)節(jié)就不過(guò)多的描述了;
從上面的流程圖可以看出,RecyclerView在滑動(dòng)時(shí)候就會(huì)進(jìn)行ViewHolder的回收,而具體的回收邏輯是在recycleViewHolderInternal()這個(gè)方法里,我們重點(diǎn)分析這個(gè)方法;
先來(lái)看一下源碼:
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
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)) {
...
//第二步
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
//第三步
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
...
}
...
}
這里主要做了三步操作:
1,第一步:
這里通過(guò)判斷mCachedViews的大小是否已經(jīng)超過(guò)最大,是的話則移除mCachedViews的第一個(gè)元素,并添加到RecycledViewPool里面去;
具體請(qǐng)看下面源碼:
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);
}
2,第二步:
這里做的操作就是將ViewHolder緩存到mCachedViews集合里面去;
3,第三步:
這里通過(guò)判斷前面如果沒(méi)有將ViewHolder緩存到mCachedViews時(shí),則把該mCachedViews緩存到RecycledViewPool里去,最終走的是下面這個(gè)方法;
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);
}
需要注意的是,RecycledViewPool的viewType,一個(gè)viewType默認(rèn)對(duì)應(yīng)可以存5個(gè)ViewHolder的緩存;
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP; // 默認(rèn)5個(gè)緩存的大??;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
當(dāng)然這個(gè)值是可以修改的,通過(guò)setMaxRecycledViews(int viewType, int max)這個(gè)方法來(lái)進(jìn)行設(shè)置;
然后到這里你會(huì)發(fā)現(xiàn),這里只用了mCachedViews和RecycledViewPool來(lái)做緩存,上面提到的Scrap部分和ViewCacheExtension部分呢?別急,后面我們繼續(xù)來(lái)分析這兩者是什么時(shí)候用到的;
1,Scrap部分
先來(lái)看一下Scrap部分,Scrap集合添加ViewHolder的方法主要是在scrapView()這個(gè)方法里面,而這個(gè)方法被getScrapOrHiddenOrCachedHolderForPosition()和scrapOrRecycleView()這個(gè)方法所調(diào)用;
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);
}
}
1,先來(lái)看一下這個(gè)getScrapOrHiddenOrCachedHolderForPosition()方法,這個(gè)方法的調(diào)用時(shí)機(jī)上面已經(jīng)提到過(guò)了,就是在獲取ViewHolder的時(shí)候,這里就不重復(fù)了;
那么我們?cè)賮?lái)看一下這個(gè)方法里面的這個(gè)scrap部分做了什么?
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
...
if (!dryRun) {
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);
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
...
return null;
}
這里通過(guò)mChildHelper的findHiddenNonRemovedView()方法來(lái)獲取一個(gè)ViewHolder,是從mHiddenViews這個(gè)集合里面獲取,而這個(gè)mHiddenViews集合里面是存儲(chǔ)的和動(dòng)畫相關(guān)的ViewHolder;
這里獲取了ViewHolder之后就通過(guò)scrapView()方法存儲(chǔ)到scrap里面去;
2,接下來(lái)分析這個(gè)scrapOrRecycleView()方法的調(diào)用時(shí)機(jī);
這個(gè)方法是由detachAndScrapAttachedViews()這個(gè)方法來(lái)調(diào)用的,而調(diào)用detachAndScrapAttachedViews()這個(gè)方法的地方是LayoutManager里的onLayoutChildren()方法,也就是說(shuō),這里的回收是通過(guò)觸發(fā)LayoutManager的布局來(lái)調(diào)用的;
這里最終回收的是通過(guò)mChildHelper.getChildAt(index)獲取的ViewHolder;
到這里,scrap部分的回收就將完了;
2,ViewCacheExtension部分
接下來(lái)分析一下ViewCacheExtension部分的回收,ViewCacheExtension這個(gè)自定義緩存的部分,在源碼里面只有取ViewHolder的邏輯,但是沒(méi)有存ViewHolder的邏輯,看來(lái)谷歌是把ViewCacheExtension回收的邏輯交給開(kāi)發(fā)者自己去實(shí)現(xiàn)了,那么這里就不過(guò)多的分析了;
那么到這里RecyclerView的緩存機(jī)制就分析完了;