在本文我們繼續(xù)學(xué)習(xí)RecyclerView緩存的相關(guān)知識客税。
緩存分為緩存取出和緩存存入惨恭,首先來分析下緩存取出:
一系草、緩存取出
在上一篇文章中,我們分析到了一個(gè)比較關(guān)鍵的方法--
layoutChunk唆涝,再來看下其源碼:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//獲取view
View view = layoutState.next(recycler);
//代碼省略......
}
這里只保留了一行很重要的代碼View view = layoutState.next(recycler);
找都,看下next方法。
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
//獲取view
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
這個(gè)next方法就是從緩存中來獲取view廊酣,首先是判斷mScrapList是否為空能耻,不為空就調(diào)用nextViewFromScrapList();方法。這里是以LinearLayoutManager來分析的亡驰,mScrapList是LinearLayoutManager的一個(gè)成員晓猛,它主要與動(dòng)畫相關(guān),因此這里就不再過多的分析它了凡辱。接著往下看占锯,調(diào)用了Recycler的getViewForPosition(int position)方法组去,Recycler是RecyclerView的一個(gè)內(nèi)部類凛剥,它主要負(fù)責(zé)RecyclerView的緩存管理熔恢。看下這個(gè)內(nèi)部類:
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;
//代碼省略......
這里對幾個(gè)成員做一下說明:
mAttachedScrap:存儲當(dāng)前還在屏幕中的ViewHolder乳乌,意思就是剛剛從屏幕中分離出來蚓曼,然后又即將添加到屏幕上的ViewHolder。
比如說钦扭,RecyclerView數(shù)據(jù)發(fā)生更新纫版,調(diào)用notifyDataSetChanged()方法刷新界面,在列表數(shù)據(jù)刷新的時(shí)候存儲屏幕內(nèi)被移除的ViewHolder客情。
mChangedScrap:存儲的是當(dāng)前被更新的ViewHolder其弊,比如說調(diào)用了adapter調(diào)用notifyItemChanged方法。
mAttachedScrap和mChangedScrap共同組成第一級緩存膀斋。也叫屏幕內(nèi)緩存梭伐,不參與RecyclerView在滑動(dòng)狀態(tài)下ViewHolder的回收和復(fù)用。
mCachedViews:默認(rèn)大小為2仰担,也叫離屏緩存糊识。即在Recycler View滑動(dòng)的時(shí)候,回收滑出屏幕的ViewHolder以及提供滑動(dòng)到屏幕內(nèi)的ViewHolder的復(fù)用摔蓝。
mCachedViews構(gòu)成第二級緩存赂苗。
mViewCacheExtension:是一個(gè)抽象靜態(tài)類,用于充當(dāng)附加的緩存池贮尉。當(dāng)RecyclerView從第一級緩存中沒有找到需要的view時(shí)拌滋,就會從ViewCacheExtension中查找。
不過這個(gè)緩存是由我們開發(fā)者自己維護(hù)的猜谚,如果沒有設(shè)置這個(gè)緩存败砂,就不會啟用它赌渣。一般情況下我們也不會去設(shè)置它,系統(tǒng)已經(jīng)預(yù)先提供了兩級緩存了昌犹。
如果有特殊需求坚芜,比如要在調(diào)用系統(tǒng)的緩存池之前,返回一個(gè)特定的視圖斜姥,才會用到它鸿竖。
RecycledViewPool:非常強(qiáng)大的緩存,其默認(rèn)大小為5疾渴,用于在多個(gè)嵌套的RecyclerView之間緩存共享的viewholder千贯,接下來會介紹它屯仗。
}
參考網(wǎng)址:https://juejin.im/entry/5c66ce2b51882562c704ebad
從上面我們可以看到搞坝,RecyclerView緩存的是viewholder,這也是它跟ListView不同的地方魁袜,ListView需要我們手動(dòng)判斷緩存是否為空桩撮,而RecyclerView由于緩存的是viewholder,直接就在內(nèi)部就幫我們判斷好了峰弹,我們只需要使用即可店量。
重點(diǎn)看下RecycledViewPool這個(gè)緩存類:
/**
* RecycledViewPool lets you share Views between multiple RecyclerViews.
* 在多個(gè)RecyclerView之間共享緩存的viewholder
* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
* and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
* <p>
* RecyclerView automatically creates a pool for itself if you don't provide one.
* 如果我們沒有為RecyclerView設(shè)置RecycledViewPool,RecyclerView會自動(dòng)為自己創(chuàng)建一個(gè)鞠呈,當(dāng)然了這個(gè)自己自動(dòng)創(chuàng)建的只能自己單獨(dú)使用
*/
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
/**
* Tracks both pooled holders, as well as create/bind timing metadata for the given type.
* <p>
* Note that this tracks running averages of create/bind time across all RecyclerViews
* (and, indirectly, Adapters) that use this pool.
* <p>
* 對于緩存的ViewHolder的統(tǒng)一管理
*/
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<>();
//代碼省略......
/**
* Sets the maximum number of ViewHolders to hold in the pool before discarding.
* 設(shè)置RecyclerViewPool能緩存的ViewHolder的最大數(shù)量融师。
*/
public void setMaxRecycledViews(int viewType, int max) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mMaxScrap = max;
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}
/**
* Returns the current number of Views held by the RecycledViewPool of the given view type.
* 獲取RecyclerViewPool緩存的ViewHolder數(shù)量。
*/
public int getRecycledViewCount(int viewType) {
return getScrapDataForType(viewType).mScrapHeap.size();
}
/**
* Acquire a ViewHolder of the specified type from the pool, or {@code null} if none are
* present.
* 從RecyclerViewPool中獲取一個(gè)ViewHolder緩存蚁吝,并將其從RecyclerViewPool緩存中移除旱爆。
*/
@Nullable
public ViewHolder getRecycledView(int viewType) {
//根據(jù)viewtype獲取緩存
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
//從pool中刪除并返回符合條件的viewholder
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
//代碼省略......
/**
* Add a scrap ViewHolder to the pool.
* <p>
* If the pool is already full for that ViewHolder's type, it will be immediately discarded.
*
* @param scrap ViewHolder to be added to the pool.
*/
public void putRecycledView(ViewHolder scrap) {
//獲取要緩存的viewholder的viewType
final int viewType = scrap.getItemViewType();
//根據(jù)viewType來獲取viewholder要保存的位置
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
//如果緩存區(qū)滿了,則什么也不做窘茁,直接返回
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
scrap.resetInternal();
//將viewholder存入緩存中
scrapHeap.add(scrap);
}
//代碼省略......
private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}
}
從名字上看怀伦,RecycledViewPool是一個(gè)緩存池,其內(nèi)部實(shí)際上是通過一個(gè)默認(rèn)大小為5的ArrayList來實(shí)現(xiàn)的山林,我們也可以手動(dòng)設(shè)置其緩存容量房待。然后ArrayList又是用ScrapData這個(gè)內(nèi)部類來統(tǒng)一管理,最后再將這個(gè)ScrapData放到SparseArray中驼抹,組成緩存的最終形勢桑孩。我們在實(shí)現(xiàn)了自己的RecycledViewPool后,只需要調(diào)用RecyclerView#setRecycledViewPool(RecycledViewPool)
就可以為多個(gè)RecyclerView設(shè)置共享的RecycledViewPool框冀。
順帶說下洼怔,這個(gè)SparseArray內(nèi)部其實(shí)是由兩個(gè)數(shù)組來實(shí)現(xiàn)的,一個(gè)數(shù)組存儲鍵左驾,另一個(gè)數(shù)組存儲值镣隶,其效率比HashMap要高一點(diǎn)极谊,但是只能存儲少量數(shù)據(jù),數(shù)據(jù)量大的話其效率不如HashMap安岂。在這里轻猖,鍵是viewType,值是scrapData域那。
分析完了Recycler及RecycledViewPool后咙边,我們繼續(xù)跟進(jìn)getViewForPosition方法:
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
最終調(diào)用了 tryGetViewHolderForPositionByDeadline方法。
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
//代碼省略......
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
//默認(rèn)情況下是false次员,只有動(dòng)畫的情況下才是true
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
//第一次嘗試败许,從attachScrap和cachedSCrap中查找viewholder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
//如果獲取到的viewholder不可用于當(dāng)前這個(gè)位置
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
//如果viewholder可以從scrap / cache 中移除
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
//移除這個(gè)viewholder
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
//將viewholder置空,表示沒有查找到淑蔚,繼續(xù)往下查找
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//代碼省略......
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
//第二次嘗試市殷,對應(yīng)hasStableId情況
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
//如果第二次嘗試后holder仍為空,并且我們設(shè)置了自定義緩存(ViewCacheExtension)
if (holder == null && mViewCacheExtension != null) {
//從ViewCacheExtension中查找
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
//根據(jù)查找到的view獲取相應(yīng)的viewholder
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());
}
}
}
//前面的緩存均為獲取到viewholder刹衫,從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);
}
}
}
//從緩存中查找失敗醋寝,調(diào)用adapter的createViewHolder方法創(chuàng)建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 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");
}
}
}
//代碼省略......
return holder;
}
我們從上往下慢慢看:
根據(jù)position獲取緩存
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
/**
* Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its
* {@link LayoutManager} layout items where they will be at the beginning of a set of
* predictive item animations.
* 在有動(dòng)畫情況下這里返回true,否則返回false
*/
public boolean isPreLayout() {
return mInPreLayout;
}
//從mChangedScrap緩存中獲取viewholder
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
//按照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;
}
首先判斷isPreLayout()是否為true带迟,這里主要是跟動(dòng)畫有關(guān)音羞,如果為true,也就是有動(dòng)畫仓犬,則調(diào)用 getChangedScrapViewForPosition(position)方法嗅绰,從這個(gè)方法的名字當(dāng)中我們也可以看出是從changedScraps緩存中獲取。
如果上面isPreLayout()為false時(shí)或者isPreLayout()為true并且沒有獲取到viewholder時(shí)搀继,開始從從attachScrap和cachedScrap緩存中查找(這里是按照position來查找的)窘面。
if (holder == null) {
//從attachScrap和cachedScrap緩存中查找viewholder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
//如果查找到viewholder不適合當(dāng)前位置
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
//dryRun標(biāo)記表示viewholder是否可以從緩存中移除,為false表示可以移除律歼,這里傳入的是false
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);
//從緩存中移除viewholder
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
//將viewholder置空民镜,繼續(xù)下面的查找
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
這里重點(diǎn)關(guān)注下getScrapOrHiddenOrCachedHolderForPosition
這個(gè)方法,從名字就可以看出是按照position來查找緩存的险毁。
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())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
//dryRun為false
if (!dryRun) {
//從hiddenView中查找view
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.
//根據(jù)查找到的view獲取viewholder
final ViewHolder vh = getChildViewHolderInt(view);
//將view從hiddenView中移除
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);
//將view添加到scrap緩存中(這里是添加到了mAttachedScrap或者mChangedScrap中)
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.
//從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
//如果查找到viewholder是有效的制圈,并且viewholder的位置和position的位置相同
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
if (!dryRun) {
//從緩存中刪除這個(gè)viewholder
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
//返回holder
return holder;
}
}
return null;
}
簡要總結(jié)下getScrapOrHiddenOrCachedHolderForPosition
的流程:
- 1、首先從mAttachedScrap中查找viewholder畔况,如果找到了并且驗(yàn)證符合條件鲸鹦,直接返回。
- 2跷跪、否則從hiddenView中查找view馋嗜,再根據(jù)這個(gè)view獲取viewholder,并將view從hiddenView中移除吵瞻,接著將viewholder保存到緩存中(mAttachedScrap或者mChangedScrap)葛菇,最后返回甘磨。
- 3、如果1和2中都沒有獲取到眯停,則從mCachedViews中查找viewholder济舆,并校驗(yàn)這個(gè)viewholder是否滿足條件,如果滿足莺债,將其從mCachedViews中刪除滋觉,并返回viewholder。
沿著tryGetViewHolderForPositionByDeadline
方法繼續(xù)往下分析齐邦,上面是按照position來查找緩存的椎侠,如果沒有查找到,那么按照stableId再次進(jìn)行查找措拇。
根據(jù)itemId和viewType獲取緩存
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//省略.......
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
首先通過mAdapter.getItemViewType(offsetPosition)這個(gè)方法獲取type我纪,這個(gè)getItemViewType也是我們設(shè)置RecyclerView列表多種布局的一個(gè)重要方法,并且后面所有緩存的獲取都是根據(jù)這個(gè)type來的儡羔。也就是說前面介紹的也就是注釋中 0)
和 1)
獲取的緩存是沒有區(qū)分type宣羊,接下來的緩存獲取都是區(qū)分type的璧诵。
接下來判斷mAdapter.hasStableIds()是否為true汰蜘。
/**
* Returns true if this adapter publishes a unique <code>long</code> value that can
* act as a key for the item at a given position in the data set. If that item is relocated
* in the data set, the ID returned for that item should be the same.
*
* @return true if this adapter's items have stable IDs
*/
public final boolean hasStableIds() {
return mHasStableIds;
}
這里關(guān)注下getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun)
這個(gè)方法,這里通過adapter的getItemId方法獲取相應(yīng)的itemId之宿,這個(gè)getItemId方法我們可重寫也可不重寫族操。
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// Look in our attached views first
final int count = mAttachedScrap.size();
//從mAttachedScrap緩存中查找
for (int i = count - 1; i >= 0; i--) {
final ViewHolder holder = mAttachedScrap.get(i);
//判斷緩存中的viewholder所在的itemId是否符合傳入的itemId
if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
//判斷type
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.
if (!mState.isPreLayout()) {
holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
}
}
//itemId和viewType都滿足,直接返回holder
return holder;
} else if (!dryRun) {
//如果viewholder的itemId符合條件比被,但是viewType不符合條件
// 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.
//從緩存中移除viewholder
mAttachedScrap.remove(i);
removeDetachedView(holder.itemView, false);
//將viewholder緩存到RecyclerViewPool中
quickRecycleScrapView(holder.itemView);
}
}
}
// Search the first-level cache
//從mCachedViews緩存中查找
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
//判斷緩存中的viewholder所在的itemId是否符合傳入的itemId
if (holder.getItemId() == id) {
//判斷type
if (type == holder.getItemViewType()) {
//如果viewholder的itemId符合條件色难,但是viewType不符合條件
if (!dryRun) {
//從緩存中移除viewholder
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
//將viewholder從mCachedViews中移除,并緩存到RecyclerViewPool中
recycleCachedViewAt(i);
return null;
}
}
}
return null;
}
這個(gè)方法其實(shí)和上面的getScrapOrHiddenOrCachedHolderForPosition
方法差不多等缀,區(qū)別是該方法是按照itemId和viewType來判斷的枷莉,而getScrapOrHiddenOrCachedHolderForPosition
是按照position來判斷的。根據(jù)itemId和viewType來查找的大致流程如下:
從自定義緩存中查找(ViewCacheExtension)
當(dāng)前面的查找都沒有找到時(shí)尺迂,程序就來到了這里笤妙,從我們的自定義緩存(ViewCacheExtension)中來查找。
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
//從ViewCacheExtension中獲取view
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
//根據(jù)view獲取viewholder
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());
}
}
}
代碼很簡單噪裕,主要是看下這個(gè)ViewCacheExtension到底是啥:
/**
* ViewCacheExtension is a helper class to provide an additional layer of view caching that can
* be controlled by the developer.
*/
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
*/
public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
}
這里就一個(gè)抽象類蹲盘,內(nèi)部一個(gè)抽象方法。對于這個(gè)自定義緩存的介紹膳音,直接引用了【進(jìn)階】RecyclerView源碼解析(二)——緩存機(jī)制里面的評價(jià):
1召衔、首先這個(gè)類基本上沒有什么限制,也就是說無論是緩存使用的數(shù)據(jù)結(jié)構(gòu)還有緩存算法(LRU還是什么)完全自定義祭陷,都由開發(fā)者自己決定苍凛,這一點(diǎn)可以說既給了開發(fā)者很大的便利趣席,也給開發(fā)者帶來了很大的隱患。
2醇蝴、對于平常的緩存吩坝,我們的理解在怎么說至少get-add|push-pop都是成對出現(xiàn),為什么這樣說的哑蔫,也就是緩存至少有進(jìn)也有出钉寝。而這里可以看到這里的抽象類只定義了出的方法,也就是只出不進(jìn)闸迷,進(jìn)的時(shí)機(jī)嵌纲,大小,時(shí)效等完全沒有規(guī)定腥沽。
在日常開發(fā)中逮走,我們也很少去自定義這一級的緩存,因?yàn)樗鼛缀跤貌簧稀?/p>
從RecyclerViewPool中查找
RecyclerView與ListView的另一個(gè)不同之處是RecyclerView提供了RecyclerViewPool今阳,這是一個(gè)在多個(gè)RecyclerView之間提供共享view的緩存师溅。比較常見的場景是RecyclerView和RecyclerView之間的嵌套,RecyclerViewPool為這些個(gè)嵌套的RecyclerView內(nèi)view的整體復(fù)用提供了便利盾舌。
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);
}
}
}
對于RecyclerViewPool在本文的前面已經(jīng)有介紹墓臭,這里就不再說了,我們繼續(xù)往下看妖谴。
調(diào)用adapter的createViewHolder創(chuàng)建ViewHolder
如果經(jīng)過上面的多級緩存查找仍然沒有查找到的話窿锉,程序最終會調(diào)用調(diào)用adapter的createViewHolder方法來創(chuàng)建一個(gè)viewholder。
if (holder == null) {
long start = getNanoTime();
//代碼省略......
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);
}
}
//代碼省略......
}
沒什么好說的膝舅,直接調(diào)用mAdapter.createViewHolder(RecyclerView.this, type)創(chuàng)建嗡载,在這個(gè)方法里面最終調(diào)用了我們繼承并實(shí)現(xiàn)的onCreateViewHolder方法。
至此仍稀,RecyclerView的緩存取出過程總算是分析完了洼滚,不過這里也只是簡要的介紹了下,還有很多細(xì)節(jié)由于水平限制沒有詳細(xì)介紹技潘。最后再總結(jié)下:
- RecyclerView一共有三級緩存(也有人說是四級緩存遥巴,這個(gè)每個(gè)人的角度不一樣,因此會有一些區(qū)別)崭篡。第一級緩存由mAttachedScrap挪哄,mChangedScrap,mCachedViews共同組成琉闪;第二級緩存是ViewCacheExtension迹炼;第三級緩存是RecyclerViewPool。
- RecyclerView在第一級緩存中查找的時(shí)候一共查找了兩次。第一次是根據(jù)position來查找的斯入,調(diào)用方法為
getScrapOrHiddenOrCachedHolderForPosition
砂碉;第二次是根據(jù)itemId和viewType來查找的,調(diào)用方法為getScrapOrCachedViewForId
刻两。 - RecyclerView在根據(jù)itemId和viewType來查找緩存時(shí)增蹭,如果發(fā)現(xiàn)當(dāng)viewholder的itemId符合條件而viewType不符合條件時(shí),會將這個(gè)viewholder從mAttachedScrap或者mCachedViews中移動(dòng)到RecyclerViewPool中(也就是將這個(gè)viewholder從mAttachedScrap或者mCachedViews中刪除同時(shí)保存到RecyclerViewPool中)磅摹。
二滋迈、緩存存入
既然有緩存取出,也應(yīng)該有緩存的存入户誓。RecyclerView緩存的存入有一個(gè)很重要的方法:recycleView(View view)
/**
* Recycle a detached view. The specified view will be added to a pool of views
* for later rebinding and reuse.
*
* <p>A view must be fully detached (removed from parent) before it may be recycled. If the
* View is scrapped, it will be removed from scrap list.</p>
*
* @param view Removed view for recycling
* @see LayoutManager#removeAndRecycleView(View, Recycler)
*/
public void recycleView(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)
//根據(jù)傳入的view獲取相關(guān)的viewholder
ViewHolder holder = getChildViewHolderInt(view);
//如果這個(gè)holder已經(jīng)被打上了清除的標(biāo)記饼灿,則將其移除
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
//如果這個(gè)holder是來自緩存的可見viewholder數(shù)組,將其移除
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
//如果這個(gè)holder是來自緩存的不可見viewholder數(shù)組帝美,將其移除
holder.clearReturnedFromScrapFlag();
}
//開始緩存
recycleViewHolderInternal(holder);
}
該方法首先根據(jù)view獲取holder碍彭,然后判斷holder是否已經(jīng)在緩存中,如果在悼潭,將其從緩存中清除庇忌,最后調(diào)用recycleViewHolderInternal方法開始緩存。
/**
* internal implementation checks if view is scrapped or attached and throws an exception
* if so.
* Public version un-scraps before calling recycle.
*/
void recycleViewHolderInternal(ViewHolder holder) {
//代碼省略(這里主要是對holder進(jìn)行了一些判斷)......
if (forceRecycle || holder.isRecyclable()) {
//如果當(dāng)前有緩存舰褪,且緩存的數(shù)量大于0皆疹,并且viewholder的flag是有效的并且不是REMOVED和UPDATE,進(jìn)行緩存
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) {
//移除緩存中的第一個(gè)view
recycleCachedViewAt(0);
cachedViewSize--;
}
//獲取要緩存的holder在緩存集合中的position
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
// when adding the view, skip past most recently prefetched views
//緩存的時(shí)候抵知,不能覆蓋最近經(jīng)常使用到的緩存
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
targetCacheIndex = cacheIndex + 1;
}
//添加緩存
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
//如果沒有緩存的話墙基,則添加到RecyclerViewPool中
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
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.
//從mViewInfoStore中移除這個(gè)holder
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
在這里可以看出软族,RecyclerView的存緩存策略使用的最少使用策略刷喜。當(dāng)我們存儲viewholder的時(shí)候,會判斷這個(gè)viewholder是否來自緩存立砸,如果是的話掖疮,那么在存緩存的時(shí)候就不能覆蓋最近比較頻繁使用的緩存。
如何判斷這個(gè)viewholder緩存最近是否頻繁使用呢颗祝?官方定義了一個(gè)輔助類--GapWorker浊闪,它有一個(gè)內(nèi)部類LayoutPrefetchRegistryImpl,然后在這個(gè)內(nèi)部類有一個(gè)成員數(shù)組int[] mPrefetchArray
螺戳,這個(gè)數(shù)組是用來記錄最近使用過的holder搁宾,所以RecyclerView在存緩存的時(shí)候會將要保存的viewholder與這個(gè)數(shù)組里面的viewholder進(jìn)行匹配,代碼如下:
boolean lastPrefetchIncludedPosition(int position) {
if (mPrefetchArray != null) {
final int count = mCount * 2;
for (int i = 0; i < count; i += 2) {
//如果viewholder是最近使用的viewholder倔幼,則返回true
if (mPrefetchArray[i] == position) return true;
}
}
return false;
}
recycleAndClearCachedViews:將CacheViews中的ViewHolder添加進(jìn)RecyclerViewHolder盖腿,然后清空CacheViews。
void recycleAndClearCachedViews() {
final int count = mCachedViews.size();
for (int i = count - 1; i >= 0; i--) {
//將mCachedViews中的viewholder存儲到RecyclerViewPool中
recycleCachedViewAt(i);
}
//清空mCachedViews集合
mCachedViews.clear();
if (ALLOW_THREAD_GAP_WORK) {
mPrefetchRegistry.clearPrefetchPositions();
}
}
recycleCachedViewAt
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
//從mCachedViews中取出viewholder
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
//將viewholder添加到RecyclerViewPool中
addViewHolderToRecycledViewPool(viewHolder, true);
//從mCachedViews中移除這個(gè)viewholder
mCachedViews.remove(cachedViewIndex);
}
addViewHolderToRecycledViewPool
void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
ViewCompat.setAccessibilityDelegate(holder.itemView, null);
}
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
holder.mOwnerRecyclerView = null;
//將viewholder存儲到RecyclerViewPool中
getRecycledViewPool().putRecycledView(holder);
}
參考文章:
【進(jìn)階】RecyclerView源碼解析(二)——緩存機(jī)制
深入淺出 RecyclerView
Android中的緩存藝術(shù),對比RecyclerView與ListView的緩存機(jī)制