RecycleBin快取機制
在學習Android開發的過程中,大家應該或多或少的會接觸到ListView這個控制元件。那麼,大家有沒有思考過,一個螢幕只能顯示一定的item,每次下滑後,消失的item去哪裡了?當再次回到原來的位置的時候,加載出來的item是重新獲取的還是從快取空間裡提取的呢?接下來,我們通過分析RecycleBin機制來探尋ListView的快取機制。
RecycleBin的基本原理
RecycleBin中有兩個陣列,mActiveViews和mScrapViews分別儲存可以直接複用的view(處於可見狀態的view)和間接複用的view(處於不可見狀態的view)。當螢幕向下滑動的時候,頂部view不可見時,會將其回收至RecycleBin中的mScrapViews陣列中進行儲存,底部需要顯示一個新的View時,會從mScrapViews中取出一個View傳至convertView進行復用。
在ListView中,重寫addView方法,當呼叫時,會丟擲異常。ListView是一幀一幀繪製的,會經歷measure->layout->draw方法。在ListView佈局的時候,會呼叫layoutChildren方法繪製子View。當剛開始執行layout的時候,ListView的children是上一幀中需要繪製的view的集合,當layout執行完畢時,children變成當前幀需要繪製的子View的集合。
當一個view不可見時,首先會將該view移至RecycleBin。會根據資料是否發生變化呼叫不同的方法。如果資料發生變化,會將所有字View移至RecycleBin的mScrapView陣列中進行儲存,資料未發生改變在將當前View放入mActiveViews陣列中。
final int firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { recycleBin.fillActiveViews(childCount, firstPosition); }
然後會將不可見的View清楚,呼叫detachAllViewFromParent()方法,將該view設定為null。
protected void detachAllViewsFromParent() {
final int count = mChildrenCount;
if (count <= 0) {
return;
}
final View[] children = mChildren;
mChildrenCount = 0;
for (int i = count - 1; i >= 0; i--) {
children[i].mParent = null;
children[i] = null;
}
當需要將一個view從RecycleBin中傳至ListView時,會根據mLayoutMode的不同情況呼叫fillUp,fillDown等方法。
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel != null) {
sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
} else {
sel = fillFromMiddle(childrenTop, childrenBottom);
}
break;
case LAYOUT_SYNC:
sel = fillSpecific(mSyncPosition, mSpecificTop);
break;
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
case LAYOUT_SPECIFIC:
sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
break;
case LAYOUT_MOVE_SELECTION:
sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
break;
default:
if (childCount == 0) {
if (!mStackFromBottom) {
final int position = lookForSelectablePosition(0, true);
setSelectedPositionInt(position);
sel = fillFromTop(childrenTop);
} else {
final int position = lookForSelectablePosition(mItemCount - 1, false);
setSelectedPositionInt(position);
sel = fillUp(mItemCount - 1, childrenBottom);
}
} else {
if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
sel = fillSpecific(mSelectedPosition,
oldSel == null ? childrenTop : oldSel.getTop());
} else if (mFirstPosition < mItemCount) {
sel = fillSpecific(mFirstPosition,
oldFirst == null ? childrenTop : oldFirst.getTop());
} else {
sel = fillSpecific(0, childrenTop);
}
}
break;
我們以fillDown方法為例子,該方法傳入兩個引數,一個是view對應adapter資料來源中的位置,和下一個view在ListView中的起點位置。當nextTop小於ListView的高度並且pos小於資料來源的資料總數時,將當前view新增進ListView並改變nextTop和pos的資料,實現pos的自增和下一個view的起點。
private View fillDown(int pos, int nextTop) {
View selectedView = null;
int end = (mBottom - mTop);
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
while (nextTop < end && pos < mItemCount) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
pos++;
}
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
接下來我們看一下makeAndAddView方法是怎麼將view新增進ListView的
在makeAndAddView中,如果資料來源沒有發生改變,我們會首先通過getActiveView方法在mActiveViews中查詢是否存在該View,如果不存在,通過呼叫obtainView方法從mScrapViews陣列中查詢是否存在。最後呼叫setupChild對viwe進行定位和量算。
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
if (!mDataChanged) {
child = mRecycler.getActiveView(position);
if (child != null) {
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
child = obtainView(position, mIsScrap);
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}