這是RecyclerView緩存機(jī)制系列文章的第三篇,系列文章的目錄如下:
引子
- 如果列表中每個(gè)移出屏幕的表項(xiàng)都直接銷毀,移入時(shí)重新創(chuàng)建,很不經(jīng)濟(jì)。所以RecyclerView引入了緩存機(jī)制。
- 回收是為了復(fù)用,復(fù)用的好處是有可能免去兩個(gè)昂貴的操作:1. 為表項(xiàng)視圖綁定數(shù)據(jù) 2. 創(chuàng)建表項(xiàng)視圖
- 下面幾個(gè)問(wèn)題對(duì)于理解“回收復(fù)用機(jī)制”很關(guān)鍵:
- what:回收什么?復(fù)用什么?
- where:回收到哪里去?從哪里獲得復(fù)用?
- when:什么時(shí)候回收?什么時(shí)候復(fù)用?
這一篇試著從已知的知識(shí)出發(fā)在源碼中尋覓未知的“RecyclerView復(fù)用機(jī)制”。
(ps: 下文中的 粗斜體字 表示引導(dǎo)源碼閱讀的內(nèi)心戲)
尋覓
觸發(fā)復(fù)用的眾多時(shí)機(jī)中必然包含下面這種:“當(dāng)移出屏幕的表項(xiàng)重新回到界面”。表項(xiàng)本質(zhì)上是一個(gè)View,屏幕上的表項(xiàng)必然需要依附于一棵View樹(shù),即必然有一個(gè)父容器調(diào)用了addView()。而 RecyclerView繼承自 ViewGroup,遂以RecyclerView.addView()為切入點(diǎn)向上搜尋復(fù)用的代碼。
在RecyclerView.java中全局搜索“addView”,發(fā)現(xiàn)RecyclerView()并沒(méi)有對(duì)addView()函數(shù)重載,但找到一處addView()的調(diào)用:
//RecyclerView是ViewGroup的子類
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
...
private void initChildrenHelper() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
...
@Override
public void addView(View child, int index) {
if (VERBOSE_TRACING) {
TraceCompat.beginSection("RV addView");
}
//直接調(diào)用ViewGroup.addView()
RecyclerView.this.addView(child, index);
if (VERBOSE_TRACING) {
TraceCompat.endSection();
}
dispatchChildAttached(child);
}
}
}
...
}
以ChildHelper.Callback.addView()為起點(diǎn)沿著調(diào)用鏈繼續(xù)向上搜尋,經(jīng)歷ChildHelper.addView()---LayoutManager.addViewInt()---LayoutManager.addView()最終到達(dá)LayoutManager.layoutChunk():
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {
//獲得下一個(gè)表項(xiàng)
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
result.mFinished = true;
return;
}
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
//將表項(xiàng)插入到列表中
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
...
}
addView(view)中傳入的view是函數(shù)layoutState.next()的返回值。猜測(cè)該函數(shù)是用來(lái)獲得下一個(gè)表項(xiàng)的。表項(xiàng)不止一個(gè),應(yīng)該有一個(gè)循環(huán)不斷的獲得下一個(gè)表項(xiàng)才對(duì)。沿著剛才的調(diào)用鏈繼續(xù)往上搜尋,就會(huì)發(fā)現(xiàn):的確有一個(gè)循環(huán)!
public class LinearLayoutManager extends RecyclerView.LayoutManager implements ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
...
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
//recyclerview 剩余空間
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
//不斷填充,直到空間消耗完畢
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
//填充一個(gè)表項(xiàng)
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
...
}
}
而fill()是在onLayoutChildren()中被調(diào)用:
/**
* Lay out all relevant child views from the given adapter.
* 布局所有給定adapter中相關(guān)孩子視圖
* 注釋太長(zhǎng)了,省略了不相關(guān)信息
* @param recycler Recycler to use for fetching potentially cached views for a
* position
* @param state Transient state of RecyclerView
*/
public void onLayoutChildren(Recycler recycler, State state) {
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
看完注釋,感覺(jué)前面猜測(cè)應(yīng)該是正確的。onLayoutChildren()是用來(lái)布局RecyclerView中所有的表項(xiàng)的?;仡^去看一下layoutState.next(),表項(xiàng)復(fù)用邏輯應(yīng)該就在其中。
/**
* Helper class that keeps temporary state while {LayoutManager} is filling out the empty
* space.
*/
static class LayoutState {
/**
* Gets the view for the next element that we should layout.
* 獲得下一個(gè)元素的視圖用于布局
* Also updates current item index to the next item, based on {@link #mItemDirection}
*
* @return The next element that we should layout.
*/
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
//調(diào)用了Recycler.getViewForPosition()
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
}
最終調(diào)用了Recycler.getViewForPosition(),Recycler是回收器的意思,感覺(jué)離想要找的“復(fù)用”邏輯越來(lái)越近了。Recycler到底是做什么用的?:
/**
* A Recycler is responsible for managing scrapped or detached item views for reuse.
* Recycler負(fù)責(zé)管理scrapped和detached表項(xiàng)的復(fù)用
* <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
* that has been marked for removal or reuse.</p>
*/
public final class Recycler {
...
}
終于找到你~~ ,Recycler用于表項(xiàng)的復(fù)用!沿著Recycler.getViewForPosition()的調(diào)用鏈繼續(xù)向下搜尋,找到了一個(gè)關(guān)鍵函數(shù)(函數(shù)太長(zhǎng)了,為了防止頭暈,只列出了關(guān)鍵節(jié)點(diǎn)):
/**
* Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
* cache, the RecycledViewPool, or creating it directly.
* 嘗試獲得指定位置的ViewHolder,要么從scrap,cache,RecycledViewPool中獲取,要么直接重新創(chuàng)建
* @return ViewHolder for requested position
*/
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
//0 從changed scrap集合中獲取ViewHolder
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
//1. 通過(guò)position從attach scrap或一級(jí)回收緩存中獲取ViewHolder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
...
}
if (holder == null) {
...
final int type = mAdapter.getItemViewType(offsetPosition);
//2. 通過(guò)id在attach scrap集合和一級(jí)回收緩存中查找viewHolder
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
...
}
//3. 從自定義緩存中獲取ViewHolder
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);
...
}
//4.從緩存池中拿ViewHolder
if (holder == null) { // fallback to pool
...
holder = getRecycledViewPool().getRecycledView(type);
...
}
//所有緩存都沒(méi)有命中,只能創(chuàng)建ViewHolder
if (holder == null) {
...
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
}
//只有invalid的viewHolder才能綁定視圖數(shù)據(jù)
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);
//獲得ViewHolder后,綁定視圖數(shù)據(jù)
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
...
return holder;
}
- 函數(shù)的名字以“tryGet”開(kāi)頭,“嘗試獲得”表示可能獲得失敗,再結(jié)合注釋中說(shuō)的:“嘗試獲得指定位置的ViewHolder,要么從scrap,cache,RecycledViewPool中,要么直接重新創(chuàng)建?!?strong>猜測(cè)scrap,cache,RecycledViewPool是回收表項(xiàng)的容器,相當(dāng)于表項(xiàng)緩存,如果緩存未命中則只能重新創(chuàng)建。
- 函數(shù)的返回值是
ViewHolder,難道回收和復(fù)用的是ViewHolder? 函數(shù)開(kāi)頭聲明了局部變量ViewHolder holder = null;最終返回的也是這個(gè)局部變量,并且有4處holder == null的判斷,這樣的代碼結(jié)構(gòu)是不是有點(diǎn)像緩存?每次判空意味著上一級(jí)緩存未命中并繼續(xù)嘗試新的獲取方法?緩存是不是有不止一種存儲(chǔ)形式? 讓我們一次一次地看:
第一次嘗試
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
...
}
只有在mState.isPreLayout()為true時(shí)才會(huì)做這次嘗試,這應(yīng)該是一種特殊情況,先忽略。
第二次嘗試
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
//下面一段代碼蘊(yùn)含著一個(gè)線索,買(mǎi)個(gè)伏筆,先把他略去
...
}
...
}
- 當(dāng)?shù)谝淮螄L試失敗后,嘗試通過(guò)
getScrapOrHiddenOrCachedHolderForPosition()獲得ViewHolder。 - 這里故意省略了一段代碼,先埋個(gè)伏筆,待會(huì)分析。先沿著獲取
ViewHolder的調(diào)用鏈繼續(xù)往下:
//省略非關(guān)鍵代碼
/**
* Returns a view for the position either from attach scrap, hidden children, or cache.
* 從attach scrap,hidden children或者cache中獲得指定位置上的一個(gè)ViewHolder
*/
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
//1.在attached scrap中搜索ViewHolder
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;
}
}
//2.從移除屏幕的視圖中搜索ViewHolder,找到了之后將他存入scrap回收集合中
if (!dryRun) {
View view = mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
final ViewHolder vh = getChildViewHolderInt(view);
mChildHelper.unhide(view);
int layoutIndex = mChildHelper.indexOfChild(view);
...
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.
//3.在緩存中搜索ViewHolder
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
...
return holder;
}
}
return null;
}
依次從三個(gè)地方搜索ViewHolder:1. mAttachedScrap 2. 隱藏表項(xiàng) 3. mCachedViews,找到立即返回。
其中mAttachedScrap和mCachedViews作為Recycler的成員變量,用來(lái)存儲(chǔ)一組ViewHolder:
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
...
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
...
RecycledViewPool mRecyclerPool;
}
- 看到這里應(yīng)該可以初步得出結(jié)論:RecyclerView回收機(jī)制中,回收復(fù)用的對(duì)象是
ViewHolder,且以ArrayList為結(jié)構(gòu)存儲(chǔ)在Recycler對(duì)象中。 -
RecycledViewPool mRecyclerPool;看著也像是回收容器,那待會(huì)是不是也會(huì)到這里拿ViewHolder? - 回到剛才埋下的伏筆,把第二次嘗試獲取
ViewHolder的代碼補(bǔ)全:
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
//下面一段代碼蘊(yùn)含這一個(gè)線索,買(mǎi)個(gè)伏筆,先把他略去
if (holder != null) {
//檢驗(yàn)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();
}
//若不滿足有效性檢驗(yàn),則回收ViewHolder
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
...
}
如果成功獲得ViewHolder則檢驗(yàn)其有效性,若檢驗(yàn)失敗則將其回收。好不容易獲取了ViewHoler對(duì)象,一言不合就把他回收?難道對(duì)所有復(fù)用的 ViewHolder 都有這么嚴(yán)格的檢驗(yàn)嗎?暫時(shí)無(wú)法回答這些疑問(wèn),還是先把復(fù)用邏輯看完吧:
第三次嘗試
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
//只有當(dāng)Adapter設(shè)置了id,才會(huì)進(jìn)行這次查找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
...
}
這一次嘗試調(diào)用的函數(shù)名(“byId”)和上一次(“byPosition”)只是后綴不一樣。上一次是通過(guò)表項(xiàng)位置,這一次是通過(guò)表項(xiàng)id。內(nèi)部實(shí)現(xiàn)也幾乎一樣,判斷的依據(jù)從表項(xiàng)位置變成表項(xiàng)id。為表項(xiàng)設(shè)置id屬于特殊情況,先忽略。
第四次嘗試
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
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) {
//獲得view對(duì)應(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());
}
}
}
...
}
經(jīng)過(guò)從mAttachedScrap和mCachedViews獲取ViewHolder未果后,繼續(xù)嘗試通過(guò)ViewCacheExtension獲?。?/p>
/**
* ViewCacheExtension is a helper class to provide an additional layer of view caching that can
* be controlled by the developer.
* ViewCacheExtension提供了額外的表項(xiàng)緩存層,用戶幫助開(kāi)發(fā)者自己控制表項(xiàng)緩存
* <p>
* When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
* first level cache to find a matching View. If it cannot find a suitable View, Recycler will
* call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
* {@link RecycledViewPool}.
* 當(dāng)Recycler從attached scrap和first level cache中未能找到匹配的表項(xiàng)時(shí),它會(huì)在去RecycledViewPool中查找之前,先嘗試從自定義緩存中查找
* <p>
*/
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
* <p>
* This method should <b>not</b> create a new View. Instead, it is expected to return
* an already created View that can be re-used for the given type and position.
* If the View is marked as ignored, it should first call
* {@link LayoutManager#stopIgnoringView(View)} before returning the View.
* <p>
* RecyclerView will re-bind the returned View to the position if necessary.
*/
public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
}
注釋揭露了很多信息:ViewCacheExtension用于開(kāi)發(fā)者自定義表項(xiàng)緩存,且這層緩存的訪問(wèn)順序位于mAttachedScrap和mCachedViews之后,RecycledViewPool之前。這和Recycler. tryGetViewHolderForPositionByDeadline()中的代碼邏輯一致,那接下來(lái)的第五次嘗試,應(yīng)該是從 RecycledViewPool 中獲取 ViewHolder
第五次嘗試
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) {
...
//從回收池中獲取ViewHolder對(duì)象
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
...
}
前四次嘗試都未果,最后從RecycledViewPool中獲取ViewHolder。稍等片刻!相對(duì)于從mAttachedScrap 和 mCachedViews 中獲取 ViewHolder,此處并沒(méi)有嚴(yán)格的檢驗(yàn)邏輯。為啥要區(qū)別對(duì)待不同的緩存?大大的問(wèn)號(hào)懸在頭頂,但現(xiàn)在暫時(shí)無(wú)法解答,還是接著看RecycledViewPool的結(jié)構(gòu)吧~
public final class Recycler {
...
RecycledViewPool mRecyclerPool;
//獲得RecycledViewPool實(shí)例
RecycledViewPool getRecycledViewPool() {
if (mRecyclerPool == null) {
mRecyclerPool = new RecycledViewPool();
}
return mRecyclerPool;
}
...
}
public static class RecycledViewPool {
...
//從回收池中獲取ViewHolder對(duì)象
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;
}
...
}
函數(shù)中只要訪問(wèn)了類成員變量,它的復(fù)雜度就提高了,因?yàn)轭惓蓡T變量的作用于超出了函數(shù)體,使得函數(shù)就和類中其他函數(shù)耦合,所以不得不進(jìn)行閱讀更多以幫助理解該函數(shù):
public static class RecycledViewPool {
//同類ViewHolder緩存?zhèn)€數(shù)上限
private static final int DEFAULT_MAX_SCRAP = 5;
/**
* Tracks both pooled holders, as well as create/bind timing metadata for the given type.
* 回收池中存放單個(gè)類型ViewHolder的容器
*/
static class ScrapData {
//同類ViewHolder存儲(chǔ)在ArrayList中
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
//每種類型的ViewHolder最多存5個(gè)
int mMaxScrap = DEFAULT_MAX_SCRAP;
}
//回收池中存放所有類型ViewHolder的容器
SparseArray<ScrapData> mScrap = new SparseArray<>();
...
//ViewHolder入池 按viewType分類入池,一個(gè)類型的ViewType存放在一個(gè)ScrapData中
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();
//回收時(shí),ViewHolder從列表尾部插入
scrapHeap.add(scrap);
}
//從回收池中獲取ViewHolder對(duì)象
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
//復(fù)用時(shí),從列表尾部獲取ViewHolder(優(yōu)先復(fù)用剛?cè)氤氐腣iewHoler)
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
}
- 上述代碼列出了
RecycledViewPool中最關(guān)鍵的一個(gè)成員變量和兩個(gè)函數(shù)。至此可以得出結(jié)論:RecycledViewPool中的ViewHolder存儲(chǔ)在SparseArray中,并且按viewType分類存儲(chǔ)(即是Adapter.getItemViewType()的返回值),同一類型的ViewHolder存放在ArrayList中,且默認(rèn)最多存儲(chǔ)5個(gè)。
創(chuàng)建ViewHolder并綁定數(shù)據(jù)
ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
...
//所有緩存都沒(méi)有命中,只能創(chuàng)建ViewHolder
if (holder == null) {
...
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
...
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
}
//如果表項(xiàng)沒(méi)有綁定過(guò)數(shù)據(jù) 或 表項(xiàng)需要更新 或 表項(xiàng)無(wú)效 且表項(xiàng)沒(méi)有被移除時(shí)綁定表項(xiàng)數(shù)據(jù)
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);
//為表項(xiàng)綁定數(shù)據(jù)
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
}
- 再進(jìn)行了上述所有嘗試后,如果依然沒(méi)有獲得
ViewHolder,只能重新創(chuàng)建并綁定數(shù)據(jù)。沿著調(diào)用鏈往下,就會(huì)找到熟悉的onCreateViewHolder()和onBindViewHolder()。 - 綁定數(shù)據(jù)的邏輯嵌套在一個(gè)大大的if中(原來(lái)并不是每次都要綁定數(shù)據(jù),只有滿足特定條件時(shí)才需要綁定。)
- 那什么情況下需要綁定,什么情況下不需要呢?這就要引出“緩存優(yōu)先級(jí)”這個(gè)概念。
緩存優(yōu)先級(jí)
緩存有優(yōu)先級(jí)一說(shuō),在使用圖片二級(jí)緩存(內(nèi)存+磁盤(pán))時(shí),會(huì)先嘗試去優(yōu)先級(jí)高的內(nèi)存中獲取,若未命中再去磁盤(pán)中獲取。優(yōu)先級(jí)越高意味著性能越好。
RecyclerView的緩存機(jī)制中是否也能套用“緩存優(yōu)先級(jí)”這一邏輯?-
雖然為了獲取
ViewHolder做了5次嘗試(共從6個(gè)地方獲?。扰懦?種特殊情況,即從mChangedScrap獲取、通過(guò)id獲取、從自定義緩存獲取,正常流程中只剩下3種獲取方式,優(yōu)先級(jí)從高到低依次是:- 從
mAttachedScrap獲取 - 從
mCachedViews獲取 - 從
mRecyclerPool獲取
- 從
-
這樣的緩存優(yōu)先級(jí)是不是意味著,對(duì)應(yīng)的復(fù)用性能也是從高到低?(復(fù)用性能越好意味著所做的昂貴操作越少)
- 最壞情況:重新創(chuàng)建
ViewHodler并重新綁定數(shù)據(jù) - 次好情況:復(fù)用
ViewHolder但重新綁定數(shù)據(jù) - 最好情況:復(fù)用
ViewHolder且不重新綁定數(shù)據(jù)
毫無(wú)疑問(wèn),所有緩存都未命中的情況下會(huì)發(fā)生最壞情況。剩下的兩種情況應(yīng)該由3種獲取方式來(lái)分?jǐn)?,猜測(cè)優(yōu)先級(jí)最低的
mRecyclerPool方式應(yīng)該命中次好情況,而優(yōu)先級(jí)最高的mAttachedScrap應(yīng)該命中最好情況,去源碼中驗(yàn)證一下: - 最壞情況:重新創(chuàng)建
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
//1.從attached scrap回收集合中
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
//只有當(dāng)holder是有效時(shí)才返回
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) {
...
//從回收池中獲取ViewHolder對(duì)象
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
//重置ViewHolder
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
...
//如果表項(xiàng)沒(méi)有綁定過(guò)數(shù)據(jù) 或 表項(xiàng)需要更新 或 表項(xiàng)無(wú)效 且表項(xiàng)沒(méi)有被移除時(shí)綁定表項(xiàng)數(shù)據(jù)
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);
//為表項(xiàng)綁定數(shù)據(jù)
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
...
}
public abstract static class ViewHolder {
/**
* This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
* are all valid.
* 綁定標(biāo)志位
*/
static final int FLAG_BOUND = 1 << 0;
/**
* This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
* are not to be trusted and may no longer match the item view type.
* This ViewHolder must be fully rebound to different data.
* 無(wú)效標(biāo)志位
*/
static final int FLAG_INVALID = 1 << 2;
//判斷ViewHolder是否無(wú)效
boolean isInvalid() {
//將當(dāng)前ViewHolder對(duì)象的flag和無(wú)效標(biāo)志位做位與操作
return (mFlags & FLAG_INVALID) != 0;
}
//判斷ViewHolder是否被綁定
boolean isBound() {
//將當(dāng)前ViewHolder對(duì)象的flag和綁定標(biāo)志位做位與操作
return (mFlags & FLAG_BOUND) != 0;
}
/**
* 將ViewHolder重置
*/
void resetInternal() {
//將ViewHolder的flag置0
mFlags = 0;
mPosition = NO_POSITION;
mOldPosition = NO_POSITION;
mItemId = NO_ID;
mPreLayoutPosition = NO_POSITION;
mIsRecyclableCount = 0;
mShadowedHolder = null;
mShadowingHolder = null;
clearPayload();
mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
clearNestedRecyclerViewIfNotNested(this);
}
}
溫故知新,回看 mRecyclerPool復(fù)用邏輯時(shí),發(fā)現(xiàn)在成功獲得ViewHolder對(duì)象后,立即對(duì)其重置(將flag置0)。這樣就滿足了綁定數(shù)據(jù)的判斷條件(因?yàn)?和非0位與之后必然為0)。
同樣的,在才mAttachedScrap中獲取ViewHolder時(shí),只有當(dāng)其是有效的才會(huì)返回。所以猜測(cè)成立:從mRecyclerPool中復(fù)用的ViewHolder需要重新綁定數(shù)據(jù),從mAttachedScrap中復(fù)用的ViewHolder不要重新出創(chuàng)建也不需要重新綁定數(shù)據(jù)。
總結(jié)
- 在
RecyclerView中,并不是每次繪制表項(xiàng),都會(huì)重新創(chuàng)建ViewHolder對(duì)象,也不是每次都會(huì)重新綁定ViewHolder數(shù)據(jù)。 RecyclerView通過(guò)Recycler獲得下一個(gè)待繪制表項(xiàng)。Recycler有4個(gè)層次用于緩存ViewHolder對(duì)象,優(yōu)先級(jí)從高到底依次為ArrayList<ViewHolder> mAttachedScrap、ArrayList<ViewHolder> mCachedViews、ViewCacheExtension mViewCacheExtension、RecycledViewPool mRecyclerPool。如果四層緩存都未命中,則重新創(chuàng)建并綁定ViewHolder對(duì)象RecycledViewPool對(duì)ViewHolder按viewType分類存儲(chǔ)(通過(guò)SparseArray),同類ViewHolder存儲(chǔ)在默認(rèn)大小為5的ArrayList中-
從
mRecyclerPool中復(fù)用的ViewHolder需要重新綁定數(shù)據(jù),從mAttachedScrap中復(fù)用的ViewHolder不要重新出創(chuàng)建也不需要重新綁定數(shù)據(jù)。
這篇文章粗略的回答了關(guān)于“復(fù)用”的4個(gè)問(wèn)題,即“復(fù)用什么?”、“從哪里獲得復(fù)用?”,“什么時(shí)候復(fù)用?”,“復(fù)用優(yōu)先級(jí)”讀到這里,可能會(huì)有很多疑問(wèn):
-
scrap view是什么? -
changed scrap view和attached scrap view有什么區(qū)別? - 復(fù)用的
ViewHolder是在什么時(shí)候被緩存的? - 為什么要4層緩存?它們的用途有什么區(qū)別?
分析完“復(fù)用”,后續(xù)文章會(huì)進(jìn)一步分析“回收”,希望到時(shí)候這些問(wèn)題都能迎刃而解。