lv的緩存
存儲 View 結(jié)構(gòu)
public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//noinspection unchecked
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
存儲View 的是ArrayList<View>[],并且數(shù)組大小為viewTypeCount , 這也是為什么我們在多 type 的時(shí)候需要指定type的個(gè)數(shù)了.
屏幕外的緩存
/**
* Put a view into the ScrapViews list. These views are unordered.
*
* @param scrap The view to add
*/
void addScrapView(View scrap, int position) {
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
return;
}
lp.scrappedFromPosition = position;
...
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
...
}
/**
* @return A view from the ScrapViews collection. These are unordered.
*/
View getScrapView(int position) {
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else {
int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
}
return null;
}
static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
int size = scrapViews.size();
if (size > 0) {
// See if we still have a view for this position.
for (int i=0; i<size; i++) {
View view = scrapViews.get(i);
if (((AbsListView.LayoutParams)view.getLayoutParams())
.scrappedFromPosition == position) {
scrapViews.remove(i);
return view;
}
}
return scrapViews.remove(size - 1);
} else {
return null;
}~~~
####注意:
代碼為api 21的,各個(gè)系統(tǒng)版本不同可能代碼有所不同,但是核心的思想是一樣的.
##rv的緩存
###根據(jù) Position 獲取 View 視圖
View getViewForPosition(int position, boolean dryRun) {
...
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 1) Find from scrap by position
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
...
}
if (holder == null) {
...
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = 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 (holder == null) { // fallback to recycler
// try recycler.
// Head to the shared pool.
if (DEBUG) {
Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
+ "pool");
}
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
...
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} 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);
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
holder.mOwnerRecyclerView = RecyclerView.this;
mAdapter.bindViewHolder(holder, offsetPosition);
attachAccessibilityDelegate(holder.itemView);
bound = true;
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
}
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrap && bound;
return holder.itemView;
}
這里可以看出
holder 分別從以下幾個(gè)地方被賦值:
1. 當(dāng)mState.isPreLayout() 為 true 有也就是動(dòng)畫的時(shí)候.
getChangedScrapViewForPosition
從mChangedScrap 中獲取到配置position ,position 配置不到的話,當(dāng)mAdapter.hasStableIds() 為 true 的話,匹配getItemId 的值.值得注意當(dāng)我們的 LayoutManger 支持動(dòng)畫的時(shí)候,他的onLayoutChildren 會被調(diào)用兩個(gè),一次為Pre-Layout,一種是 Real-Layout, 而mChangedScrap中的 View 在只會在Pre-Layout.返回的目的是為了 LayoutManager 在Pre-Layout中不會空白了一塊.可以正確布局.
* getScrapViewForPosition() 從mAttachedScrap 中匹配position , 配置不到的話從mCachedViews 去匹配 position 值,
* 當(dāng)mAdapter.hasStableIds() 為 true 的時(shí)候.
getScrapViewForId 從mAttachedScrap 中匹配getItemId 以及 ViewType 值,匹配不到的話,嘗試從mCachedViews 匹配getItemId 和ViewType.
* 當(dāng) mViewCacheExtension 不為空的時(shí)候 getViewForPositionAndType()從開發(fā)者設(shè)置ViewCacheExtension 中獲取到 View
* getRecycledViewPool().getRecycledView(type)
從RecycledViewPool 獲取到View
* mAdapter.createViewHolder(RecyclerView.this, type); 創(chuàng)建一個(gè) View .
我們從上面的是地方可以看出我們的緩存 View 存儲在兩種類型:
Scrap 和recycle:
Scrap
mChangedScrap,mAttachedScrap,mCachedViews.
recycle
RecycledViewPool.
Scrap 之所以比recycle輕量. 因?yàn)閞ecycle 一定會有bindViewHolder 的動(dòng)作.而Scrap 不一定會有.
####注意:
mAdapter.hasStableIds() 表示數(shù)據(jù)集合中的每一項(xiàng)是否可以代表有惟一的標(biāo)識符,這個(gè)都作用跟Adapter.hasStableIds一致的效果,具體作用在notifyDataSetChanged 體現(xiàn). eg:你有適配器hasStableIds為 false, 你的列表中刪除了第2項(xiàng),那你使用notifyDataSetChanged 那么你的第2項(xiàng)的展示的數(shù)據(jù)是第三項(xiàng)的,但是你的 View 還是之前的第2的View.而你hasStableIds 為 true, 并且為他們每個(gè)項(xiàng)有一個(gè)唯一的 id, 那你刪除了第2項(xiàng),使用notifyDataSetChanged 那么你的第2項(xiàng)的展示的數(shù)據(jù)是第三項(xiàng)的,你的 View 就是之前第三項(xiàng).因?yàn)?View 跟數(shù)據(jù)匹配上了.
###屏幕內(nèi)緩存
RequestLayout 和NotifyXXX 下的回收.
```
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
...
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
```
mChangedScrap: 收集的是界面上被打上UpdateOp.UPDATE的 item,rv 通過notifyItemChanged對 position 所在的ViewHolder 打上flag的.
mAttachedScrap: 界面上所有非mChangedScrap 的 View
###屏幕外的緩存.
private static final int DEFAULT_CACHE_SIZE = 2;
private int mViewCacheMax = DEFAULT_CACHE_SIZE;
/**
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) {...
if (forceRecycle || holder.isRecyclable()) {
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE)) {
// Retire oldest cached view
final int cachedViewSize = mCachedViews.size();
if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
}
…
}
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder);
mCachedViews.remove(cachedViewIndex);
}
void addViewHolderToRecycledViewPool(ViewHolder holder) {
ViewCompat.setAccessibilityDelegate(holder.itemView, null);
dispatchViewRecycled(holder);
holder.mOwnerRecyclerView = null;
getRecycledViewPool().putRecycledView(holder);
}
這里我們可以看出 view 的回收是要經(jīng)過mCachedViews 然后才是RecycledViewPool
并且這里的判斷條件也是挺有意思:
如果mCachedViews 到達(dá) 最大值,講 mCachedViews 第一個(gè)壓入RecycledViewPool中,然后要回收的 View也壓到RecycledViewPool中去.如果不沒有到達(dá)最大值才壓入 mCachedViews 中去.從代碼中我們可以看出最大值為2,你也可是使用setViewCacheSize方法設(shè)置最大值.
RecycledViewPool 是最后一級回收了.我們看一下這個(gè)RecycledViewPool 的實(shí)現(xiàn).
public static class RecycledViewPool {
private SparseArray<ArrayList<ViewHolder>> mScrap =
new SparseArray<ArrayList<ViewHolder>>();
private SparseIntArray mMaxScrap = new SparseIntArray();
private int mAttachCount = 0;
private static final int DEFAULT_MAX_SCRAP = 5;
...
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapHeapForType(viewType);
if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
…
private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
ArrayList<ViewHolder> scrap = mScrap.get(viewType);
if (scrap == null) {
scrap = new ArrayList<>();
mScrap.put(viewType, scrap);
if (mMaxScrap.indexOfKey(viewType) < 0) {
mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
}
}
return scrap;
}
}
我們可以看出這里使用了SparseArray<ArrayList<ViewHolder>>來存儲 View,由于SparseArray 可以動(dòng)態(tài)增加,所以我們并不需要手動(dòng)寫明 viewTypeCount. 同時(shí)我們也可以看到每種類型緩存最大值為5 ,大于5以后 的 view 會被丟棄.
##對比:
1. lv為一 View 為單位. rv 以 ViewHolder 為單位. 設(shè)計(jì)上 rv 更先進(jìn).
* lv對多 type 的緩存機(jī)制不太好, 只要被生成 View 都會被緩存起來.
eg:當(dāng)出現(xiàn)大量 type 1 出現(xiàn)以后,在出現(xiàn)大量的 type2, 此時(shí)內(nèi)存中就還有存在大量的 type1和大量的 type2. 而我們現(xiàn)在只有 type2,多余的 type1 一直占有內(nèi)存不釋放..而rv 的滑動(dòng)時(shí)候的緩存是RecycledViewPool +mCachedViews , mCachedViews只有2個(gè),而RecycledViewPool相同 type 最多存儲5個(gè).也就像上面的場景, rv 就不會有大量的 type1和 type2 的出現(xiàn).
* rv 的緩存定制能力更強(qiáng).你可以自定義一個(gè)RecycledViewPool 進(jìn)去,也能設(shè)置mCachedViews 的容量.
##rv 使用的坑:
1. 當(dāng)你的 多type的是個(gè),相同 type 出現(xiàn)在屏幕的數(shù)量差值大于5 的時(shí)候,并且經(jīng)常出現(xiàn)的這種情況.比如說你的 type1 這時(shí)候在屏幕中是有13個(gè),然后變成3,然后再變成13,這中情況交替出現(xiàn)的時(shí)候,會出現(xiàn)頻繁的 View 的創(chuàng)建.因?yàn)槟阍?3 切換到3 的時(shí)候,剩下的10要被緩存起來,但是RecycledViewPool只能緩存5個(gè),mCachedViews最多幫助緩存2個(gè),剩下的 View 就被釋放了.當(dāng)再次切換到13的情況下,就只能創(chuàng)建 View 了,我們可以通過setMaxRecycledViews對RecycledViewPool 緩存最大值的修改.
* 出于動(dòng)畫的考量.當(dāng)你的 數(shù)據(jù)的改變而你調(diào)用notifyItemChanged 的時(shí)候.因?yàn)榇藭r(shí)的 View 被 mChangedScrap 儲存.而且mChangedScrap只會在 pre-Layout 中返回,導(dǎo)致你在 real-layout 中得到 View 是一個(gè)新的 View, 所以notifyItemChanged 往往導(dǎo)致了一些 View 的創(chuàng)建和界面的圖片的閃爍.