本系列博客基于
com.android.support:recyclerview-v7:26.1.0
1.【進(jìn)階】RecyclerView源碼解析(一)——繪制流程
2.【進(jìn)階】RecyclerView源碼解析(二)——緩存機(jī)制
3.【進(jìn)階】RecyclerView源碼解析(三)——深度解析緩存機(jī)制
4.【進(jìn)階】RecyclerView源碼解析(四)——RecyclerView進(jìn)階優(yōu)化使用
5.【框架】基于AOP的RecyclerView復(fù)雜樓層樣式的開發(fā)框架难菌,樓層打通,支持組件化蔑滓,支持MVP(不用每次再寫Adapter了~)
接著上一篇博客分析完RecyclerView的繪制流程郊酒,其實對RecyclerView已經(jīng)有了一個大體的了解,尤其是RecyclerView和LayoutManager和ItemDecoration的關(guān)系烫饼。 本篇文章將對RecyclerView的緩存機(jī)制的講解猎塞,但由于緩存對于RecyclerView非常重要,所以準(zhǔn)備分幾部分進(jìn)行分析杠纵,本篇博客主要從源碼角度進(jìn)行分析緩存的流程荠耽。
前言
無論是原來使用的ListView還是RecyclerView,列表類型的視圖一直是原生使用的一個重頭戲比藻。無論是從使用功能上還是性能上铝量,原生的列表視圖都有著巨大的優(yōu)勢,而這個優(yōu)勢很重要的一方面其實就是對于視圖的復(fù)用機(jī)制银亲,也就是緩存慢叨。從ListView的RecycleBin到RecyclerView的Recycler,Google對于列表視圖的緩存的設(shè)計一直非澄耱穑考究值得我們學(xué)習(xí)和研究拍谐。而網(wǎng)頁的H5和火熱的RN對于復(fù)雜的列表視圖的渲染性能不好從這里面其實也可以尋找到一些原因。
總流程圖
放上一張Bugly的一篇博客對RecyclerView的緩存的流程圖吧(自己畫發(fā)現(xiàn)差不多就直接挪過來了...若侵立刪)
源碼分析
如果看過上一篇博客的人應(yīng)該還記得我們當(dāng)中提到了和緩存機(jī)制有關(guān)的那個函數(shù)馏段。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//next方法很重要
View view = layoutState.next(recycler);
//執(zhí)行addView
//執(zhí)行measureChild操作
這里再放上這行代碼轩拨,沒錯就是next函數(shù)。
View next(RecyclerView.Recycler recycler) {
//默認(rèn)mScrapList=null院喜,但是執(zhí)行l(wèi)ayoutForPredictiveAnimations方法的時候不會為空
if (mScrapList != null) {
return nextViewFromScrapList();
}
//重要亡蓉,從recycler獲得View,mScrapList是被LayoutManager持有,recycler是被RecyclerView持有
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
而next函數(shù)這里也放了上來喷舀,其實可以看到砍濒,除了我們平常認(rèn)知的RecyclerView中Recycler的緩存,這里其實還存在一級的緩存mScrapList硫麻,mScrapList是被LayoutManager持有爸邢,recycler是被RecyclerView持有。但是mScrapList其實一定程度上和動畫有關(guān)拿愧,這里就不做分析了杠河,所以可以看到,緩存的重頭戲還是在RecyclerView中的內(nèi)部類Recycler中。這里先對Recycler這個內(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;
...
}
類的結(jié)構(gòu)也比較清楚,這里可以清楚的看到我們后面講到的四級緩存機(jī)制所用到的類都在這里可以看到:
* 1.一級緩存:mAttachedScrap
* 2.二級緩存:mCacheViews
* 3.三級緩存:mViewCacheExtension
* 4.四級緩存:mRecyclerPool
繼續(xù)跟進(jìn)getViewForPosition方法奢赂,其實可以發(fā)現(xiàn)最后進(jìn)入的是tryGetViewHolderForPositionByDeadline方法陪白。
/**
* Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
* cache, the RecycledViewPool, or creating it directly.
**/
/**
* 注釋寫的很清楚,從Recycler的scrap膳灶,cache咱士,RecyclerViewPool,或者直接create創(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
if (mState.isPreLayout()) {
//preLayout默認(rèn)是false,只有有動畫的時候才為true
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
//如果檢查發(fā)現(xiàn)這個holder不是當(dāng)前position的
// 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);
//從scrap中移除
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
//放到ViewCache或者Pool中
recycleViewHolderInternal(holder);
}
//至空繼續(xù)尋找
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
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
if (mAdapter.hasStableIds()) {
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.
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());
}
}
}
//pool
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);
}
}
}
//create
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);
}
}
....
return holder;
}
刪除了得到holder后的代碼(其實還想再刪點的...)轧钓,本篇博客主要是對緩存機(jī)制源碼的分析序厉。對于源碼的一個方法第一眼先看一下方法的注釋,這里專門把方法的注釋放了上來毕箍,可以發(fā)現(xiàn)注釋寫的很清楚從Recycler的scrap弛房,cache,RecyclerViewPool,或者直接create創(chuàng)建而柑,這可以說是對RecyclerView緩存流程的概述:四級緩存(不知道為什么官方的注釋沒有寫上自定義緩存...)文捶。接下來就一級一級分析吧。
if (mState.isPreLayout()) {
//preLayout默認(rèn)是false媒咳,只有有動畫的時候才為true
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
首先可以看到這里有個判斷粹排,當(dāng)為true的時候也可以拿到holder,但是這里我們沒有并到常規(guī)緩存里面涩澡,首先可以看一下判斷條件是對mInPreLayout變量的判斷顽耳,mInPreLayout默認(rèn)是false,只有有動畫的時候才為true妙同。其次對于getChangedScrapViewForPosition方法射富,其實是從Recycler類中的mChangedScrap獲取ViewHolder,這也是為什么我們剛才沒有將mChangedScrap放到常規(guī)緩存里面渐溶。
第一次嘗試(從mAttachedScrap和mCacheView中)
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
//如果檢查發(fā)現(xiàn)這個holder不是當(dāng)前position的
...
//從scrap中移除
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
...
}
//放到ViewCache或者Pool中
recycleViewHolderInternal(holder);
}
//至空繼續(xù)尋找
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
先大體看一下第一級緩存辉浦,可以看到,這里通過getScrapOrHiddenOrCachedHolderForPosition方法來獲取ViewHolder茎辐,并檢驗holder的有效性宪郊,如果無效,則從mAttachedScrap中移除拖陆,并加入到mCacheViews或者Pool中弛槐,并且將holder至null,走下一級緩存判斷依啰。
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
//---------------------------------------------------------
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
//先從scrap中尋找
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
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.
//通過View的LayoutParam獲得ViewHolder
final ViewHolder vh = getChildViewHolderInt(view);
//從HiddenView中移除
mChildHelper.unhide(view);
....
mChildHelper.detachViewFromParent(layoutIndex);
//添加到Scrap中,其實這里既然已經(jīng)拿到了ViewHolder速警,可以直接傳vh進(jìn)去
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.
//從CacheView中拿
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
//holder是有效的叹誉,并且position相同
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
}
}
return null;
}
這里可以看到也分了三個步驟:
- 1.從mAttachedScrap中獲取
- 2.從HiddenView中獲取
-
3.從CacheView獲取
關(guān)鍵的代碼注釋我已經(jīng)放上了鸯两,流程上可以用下面這個圖來理解:
第一次判斷
第二次嘗試(對應(yīng)hasStablelds情況)
if (holder == null) {
...
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);熟悉的方法有木有,可以看到這里調(diào)用了我們平常使用RecyclerView進(jìn)行多樣式item的方法长豁,也就是說前面對于一級緩存mAttachedScrap和mCacheViews是不區(qū)分type的钧唐,從現(xiàn)在開始的判斷是區(qū)分type的緩存。這里對于我們研究多type類型的RecyclerView很有幫助匠襟。
接下來的判斷可以看到很明顯這是對于我們重寫hasStableIds()方法為true的情況钝侠。
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// Look in our attached views first
//
final int count = mAttachedScrap.size();
for (int i = count - 1; i >= 0; i--) {
//在attachedScrap中尋找
final ViewHolder holder = mAttachedScrap.get(i);
if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
//id相同并且不是從scrap中返回的
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);
}
}
return holder;
} else if (!dryRun) {
// 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.
//從scrap中移除
mAttachedScrap.remove(i);
removeDetachedView(holder.itemView, false);
//加入cacheView或者pool
quickRecycleScrapView(holder.itemView);
}
}
}
//從cacheView中找
// Search the first-level cache
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder.getItemId() == id) {
if (type == holder.getItemViewType()) {
if (!dryRun) {
//從cache中移除
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
//從cacheView中移除,但是放到pool中
recycleCachedViewAt(i);
return null;
}
}
}
return null;
}
可以看到這里的判斷其實和上面那一次差不多酸舍,需要注意的是多了對于id的判斷和對于type的判斷帅韧,也就是當(dāng)我們將hasStableIds()設(shè)為true后需要重寫holder.getItemId() 方法,來為每一個item設(shè)置一個單獨的id啃勉。具體流程圖如下:
第三次嘗試(對應(yīng)于自定義緩存)
其實這種對于我們平常的使用來說已經(jīng)很陌生了忽舟,甚至很多人都不知道RecyclerView的這一項特性。
//自定義緩存
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());
}
}
}
這里對于流程的理解沒有什么好說的璧亮,我們可以看一下這個自定義緩存的類ViewCacheExtension萧诫。
public abstract static class ViewCacheExtension {
public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
}
可以看到這里類很恐怖,為什么這樣說哪枝嘶?
1.首先這個類基本上沒有什么限制帘饶,也就是說無論是緩存使用的數(shù)據(jù)結(jié)構(gòu)還有緩存算法(LRU還是什么)完全自定義,都由開發(fā)者自己決定群扶,這一點可以說既給了開發(fā)者很大的便利及刻,也給開發(fā)者帶來了很大的隱患。
2.對于平常的緩存竞阐,我們的理解在怎么說至少get-add|push-pop都是成對出現(xiàn)缴饭,為什么這樣說的,也就是緩存至少有進(jìn)也有出骆莹。而這里可以看到這里的抽象類只定義了出的方法颗搂,也就是只出不進(jìn),進(jìn)的時機(jī)幕垦,大小丢氢,時效等完全沒有規(guī)定。
第四次嘗試(對應(yīng)于Pool)
終于到了最后一次的嘗試先改,這個緩存是針對Pool的疚察,可以說RecyclerView內(nèi)部提供的Pool是RecyclerView的一大特性,這也是和ListView不同的地方仇奶,RecyclerView提供了這種緩存形式貌嫡,支持多個RecyclerView之間復(fù)用View,也就是說通過自定義Pool我們甚至可以實現(xiàn)整個應(yīng)用內(nèi)的RecyclerView的View的復(fù)用。
if (holder == null) { // fallback to pool
......
holder = getRecycledViewPool().getRecycledView(type);
......
}
同樣這里對于流程沒有什么好說的了岛抄,可以看一下RecyclerPool的類的結(jié)構(gòu)别惦。
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
private int mAttachCount = 0;
...
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;
}
......
}
可以看到RecyclerdViewPool內(nèi)部使用到了Google推薦的數(shù)據(jù)結(jié)構(gòu)類型SparseArray,而SparseArray內(nèi)部的key就是我們的ViewType夫椭,而value存放的是ArrayList<ViewHolder>步咪。而默認(rèn)的每個ArrayList<ViewHolder>的大小是5個。這里還有一個要注意的點就是getRecycledView這個方法可以看到拿到viewholder其實是通過remove拿到的益楼,也就是通過remove拿到的。
最終創(chuàng)建
//create
if (holder == null) {
......
holder = mAdapter.createViewHolder(RecyclerView.this, type);
......
}
終于看到了我們經(jīng)常重寫的方法createViewHolder点晴,當(dāng)所有的的嘗試從緩存中獲取都失敗后感凤,只能調(diào)用我們自己重寫的createViewHolder方法,重新創(chuàng)建一個粒督。
總結(jié)
本篇博客主要從源碼的角度將RecyclerView內(nèi)部的緩存獲取的流程梳理了一遍陪竿,對于RecyclerView的緩存機(jī)制還遠(yuǎn)遠(yuǎn)不止如此,后面還會從別的角度學(xué)習(xí)RecyclerView的緩存機(jī)制屠橄。從這篇博客主要能看到以下幾點:
1.RecyclerView內(nèi)部大體可以分為四級緩存:mAttachedScrap,mCacheViews,ViewCacheExtension,RecycledViewPool.
2.mAttachedScrap,mCacheViews在第一次嘗試的時候只是對View的復(fù)用族跛,并且不區(qū)分type,但在第二次嘗試的時候是區(qū)分了Type锐墙,是對于ViewHolder的復(fù)用礁哄,ViewCacheExtension,RecycledViewPool是對于ViewHolder的復(fù)用,而且區(qū)分type溪北。
3.如果緩存ViewHolder時發(fā)現(xiàn)超過了mCachedView的限制桐绒,會將最老的ViewHolder(也就是mCachedView緩存隊列的第一個ViewHolder)移到RecycledViewPool中。
相關(guān)
基于AOP的RecyclerView復(fù)雜樓層樣式的開發(fā)框架之拨,樓層打通茉继,支持組件化,支持MVP(不用每次再寫Adapter了~)-EMvp
Star??支持一下~
歡迎提issues討論~