復(fù)用和回收
復(fù)用的好處:
避免為表項(xiàng)視圖綁定數(shù)據(jù)沪蓬,創(chuàng)建表項(xiàng)視圖畸冲。
子item的繪制交給LayoutManager去處理址晕。
fill
LinearLayoutManager#fill
作用:回收和復(fù)用萍悴。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
// 當(dāng)前的方向上是否還有多余的空間填充item
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
// 當(dāng)剩余空間> 0時(shí)春弥,繼續(xù)填充更多表項(xiàng)
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// 通過View循環(huán)虫碉,來對(duì)條目進(jìn)行一條條復(fù)用贾惦,填充剩余空間
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// 從剩余空間中扣除新表項(xiàng)占用像素值
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
// 在limit上追加新表項(xiàng)所占像素值
// 回收哪些項(xiàng)目是根據(jù)limit線走的,手指向上滑敦捧,底部填充元素须板,limit線會(huì)下移,在這根線上面的條目會(huì)被回收绞惦。
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 回收
recycleByLayoutState(recycler, layoutState);
}
}
return start - layoutState.mAvailable;
}
回收
LinearLayoutManager#recycleByLayoutState
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
// 從列表頭回收
recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
} else {
// 從列表尾回收
recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
}
}
LinearLayoutManager#recycleViewsFromStart
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
//從頭開始遍歷 LinearLayoutManager逼纸,以找出應(yīng)該會(huì)回收的表項(xiàng)
final int childCount = getChildCount();
// 是否反轉(zhuǎn)布局,就是布局上從上往下填充還是從下往上填充
if (mShouldReverseLayout) {
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
// 當(dāng)某表項(xiàng)底部位于limit隱形線之后時(shí)济蝉,回收它以上的所有表項(xiàng)
// limit是列表中隱形的線
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
//回收索引為末尾到i-1的表項(xiàng)
recycleChildren(recycler, childCount - 1, i);
return;
}
}
} else {
//回收索引為0到i-1的表項(xiàng)
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
recycleChildren(recycler, 0, i);
return;
}
}
}
}
“從列表頭回收表項(xiàng)”所對(duì)應(yīng)的場景是:手指上滑杰刽,列表向上滾動(dòng),新的表項(xiàng)逐個(gè)插入到列表尾部王滤,列表頭部的表項(xiàng)逐個(gè)被回收贺嫂。
復(fù)用
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// 1. 通過緩存池中獲取下個(gè)條目
View view = layoutState.next(recycler);
// 2. 將列表中的一項(xiàng)添加進(jìn)RecyclerView
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// 3. 測量該視圖
measureChildWithMargins(view, 0, 0);
// 4. 獲取填充視圖需要消耗的像素值
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
// 5. 布局表項(xiàng)
// 確定表項(xiàng)上下左右四個(gè)點(diǎn)相對(duì)于RecyclerView的位置
layoutDecoratedWithMargins(view, left, top, right, bottom);
}
LinearLayoutManager#next
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
RecyclerView#tryGetViewHolderForPositionByDeadline
復(fù)用機(jī)制代碼
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
// 復(fù)用的對(duì)象是ViewHolder
// 在布局之前
if (mState.isPreLayout()) {
// 1. 通過id或者position從mChangedScrap緩存找到對(duì)應(yīng)的緩存
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
if (holder == null) {
// 2. 通過position從mAttachedScrap或二級(jí)回收緩存中獲取ViewHolder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
if (!dryRun) {
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
final int type = mAdapter.getItemViewType(offsetPosition);
// 3. 通過id從mAttachedScrap或二級(jí)回收緩存中獲取ViewHolder
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) {
// 4. 從自定義緩存中獲取ViewHolder
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
if (holder == null) { // fallback to pool
// 5. 從緩存池中取ViewHolder
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
long start = getNanoTime();
// 6.所有緩存都沒命中,就需要?jiǎng)?chuàng)建ViewHolder
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");
}
}
}
if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (mState.mRunSimpleAnimations) {
int changeFlags = ItemAnimator
.buildAdapterChangeFlagsForAnimations(holder);
changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
holder, changeFlags, holder.getUnmodifiedPayloads());
recordAnimationInfoIfBouncedHiddenView(holder, info);
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//獲得ViewHolder后雁乡,綁定視圖數(shù)據(jù)
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
...
return holder;
}
總結(jié):
RecyclerView在滾動(dòng)發(fā)生之前第喳,會(huì)根據(jù)預(yù)計(jì)滾動(dòng)位移大小來決定需要向列表中填充多少新的表項(xiàng)。在填充表項(xiàng)的同時(shí)踱稍,也會(huì)回收表項(xiàng)曲饱,回收的依據(jù)是limit隱形線。
limit隱形線是RecyclerView在滾動(dòng)發(fā)生之前根據(jù)滾動(dòng)位移計(jì)算出來的一條線珠月,它是決定哪些表項(xiàng)該被回收的重要依據(jù)扩淀。它可以理解為:隱形線當(dāng)前所在位置,在滾動(dòng)完成后會(huì)和列表頂部重疊啤挎。
limit隱形線的初始值=列表當(dāng)前可見表項(xiàng)的底部到列表底部的距離驻谆,即列表在不填充新表項(xiàng)時(shí),可以滑動(dòng)的最大距離。每一個(gè)新填充表項(xiàng)消耗的像素值都會(huì)被追加到limit值之上胜臊,即limit隱形線會(huì)隨著新表項(xiàng)的填充而不斷地下移勺卢。
觸發(fā)回收邏輯時(shí),會(huì)遍歷當(dāng)前所有表項(xiàng)象对,若某表項(xiàng)的底部位于limit隱形線下方黑忱,則該表項(xiàng)上方的所有表項(xiàng)都會(huì)被回收。
四級(jí)緩存
// detach調(diào)用
// 復(fù)用的時(shí)候不需要調(diào)用bindViewHolder重新綁定數(shù)據(jù)织盼,狀態(tài)和數(shù)據(jù)不會(huì)被重置的
// 保存原封不動(dòng)的ViewHolder
// 生命周期兩次布局
// 位置一致才能復(fù)用
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
// 發(fā)生變化的ViewHolder
// 生命周期只有預(yù)布局
ArrayList<ViewHolder> mChangedScrap = null;
// remove調(diào)用
// 可通過setItemCacheSize調(diào)整杨何,默認(rèn)大小為2
// 上下滑動(dòng)酱塔,被滑出去的ViewHolder緩存
// 如果超過限制沥邻,會(huì)把最老的item移除到RecycledViewPool中。
// mCachedViews中緩存的ViewHolder只能復(fù)用于指定位置羊娃,不需要調(diào)用bindViewHolder重新綁定數(shù)據(jù)
// 應(yīng)用場景列表回滾
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
// 自定義拓展View緩存
private ViewCacheExtension mViewCacheExtension;
// RecycledViewPool中的ViewHolder存儲(chǔ)在SparseArray中唐全,并且按viewType分類存儲(chǔ)
// 同一類型的ViewHolder存放在ArrayList 中,且默認(rèn)最多存儲(chǔ)5個(gè)蕊玷。
// mCachedViews緩存放不下的時(shí)候邮利,才會(huì)把緩存放進(jìn)mRecyclerPool,里面的緩存都是需要重新綁定數(shù)據(jù)的垃帅。
// 從mRecyclerPool中取出的ViewHolder只能復(fù)用于相同viewType的表項(xiàng)延届。
RecycledViewPool mRecyclerPool;
最差情況:重新創(chuàng)建ViewHolder綁定數(shù)據(jù)
次好情況:復(fù)用ViewHolder需要重新綁定數(shù)據(jù)
最好情況:復(fù)用ViewHolder不需要重新綁定數(shù)據(jù)
談?wù)刴ChangedScrap
生命周期只有預(yù)布局的時(shí)候。
mChangedScrap的調(diào)用場景是notifyItemChanged和notifyItemRangeChanged贸诚,只有發(fā)生變化的ViewHolder才會(huì)放入到mChangedScrap中方庭。
mChangedScrap緩存中的ViewHolder是需要調(diào)用onBindViewHolder方法重新綁定數(shù)據(jù)的。
淺談幾種更新RecyclerView的區(qū)別
notifyItemInserted
需要重新布局酱固,A械念、B、C都可以從mAttachedScrap緩存拿出來直接使用运悲,不需要綁定龄减,a需要?jiǎng)?chuàng)建對(duì)應(yīng)的ViewHolder重新綁定,添加進(jìn)一級(jí)緩存班眯。
注意如果是A希停,B,C署隘,D移除B宠能,B還是在mAttachedScrap緩存,只不過FLAG是REMOVE定踱。
notifyDataSetChanged
代表數(shù)據(jù)全面發(fā)生變化棍潘,屏幕上的內(nèi)容標(biāo)為無效,屏幕上的元素全部緩存到四級(jí)緩存RecycledViewPool,屏幕上的元素都需要重新綁定。
notifyItemChanged
A被添加了FLAG_UPDATE亦歉,在scrapView(View view)中不滿足!holder.isUpdated()所以會(huì)被放入到mChangedScrap恤浪,然后在緩存復(fù)用時(shí)B、C肴楷、D都可以直接使用水由,A因?yàn)楸恍薷牧怂孕枰匦陆壎ㄒ幌隆?br> 也就是說:notifyItemChanged將屏幕上的元素保存到一級(jí)緩存中,有更改的保存到mChangedScrap中并且需要重新綁定赛蔫,沒有變化的保存到mAttachedScrap中砂客。
重點(diǎn)類
Recycler:管理復(fù)用
LayoutManager:管理布局
detach和remove
一個(gè)View只是暫時(shí)被清除掉,稍后立刻就要用到呵恢,使用detach鞠值。它會(huì)被緩存進(jìn)scrapCache的區(qū)域。
一個(gè)View不再顯示在屏幕上渗钉,需要被清除掉彤恶,并且下次再顯示它的時(shí)機(jī)目前未知,使用remove鳄橘。它會(huì)被以viewType分組声离,緩存進(jìn)RecyclerViewPool里。
scrap view的生命周期
在將表項(xiàng)一個(gè)個(gè)填充到列表之前會(huì)先將其先回收到mAttachedScrap中瘫怜,回收數(shù)據(jù)的來源是LayoutManager的孩子术徊,而LayoutManager的孩子都是屏幕上可見的或即將可見的表項(xiàng)。
RecyclerView布局的最后一步鲸湃,清除scrap view赠涮。
mAttachedScrap生命周期起始于RecyclerView布局開始,終止于RecyclerView布局結(jié)束唤锉。
RecyclerView的動(dòng)畫
列表中有兩個(gè)表項(xiàng)(1世囊、2),刪除2窿祥,此時(shí)3會(huì)從屏幕底部平滑地移入并占據(jù)原來2的位置株憾。
為了實(shí)現(xiàn)該效果,RecyclerView的策略是:為動(dòng)畫前的表項(xiàng)先執(zhí)行一次pre-layout晒衩,將不可見的表項(xiàng)3也加載到布局中嗤瞎,形成一張布局快照(1、2听系、3)贝奇。再為動(dòng)畫后的表項(xiàng)執(zhí)行一次post-layout,同樣形成一張布局快照(1靠胜、3)掉瞳。比對(duì)兩張快照中表項(xiàng)3的位置毕源,就知道表項(xiàng)3該如何做動(dòng)畫了,表項(xiàng)2做消失動(dòng)畫陕习,當(dāng)動(dòng)畫結(jié)束后霎褐,item2的ViewHolder會(huì)被回收。
RecyclerView為了實(shí)現(xiàn)表項(xiàng)動(dòng)畫该镣,進(jìn)行了2次布局(預(yù)布局+后布局)冻璃,在源碼上表現(xiàn)為LayoutManager.onLayoutChildren()被調(diào)用2次。
預(yù)布局的過程始于RecyclerView.dispatchLayoutStep1()损合,終于RecyclerView.dispatchLayoutStep2()省艳。
在每次向RecyclerView填充表項(xiàng)之前都會(huì)先清空LayoutManager中現(xiàn)存表項(xiàng),將它們detach并同時(shí)緩存入mAttachedScrap列表中嫁审。在緊接著的填充表項(xiàng)階段跋炕,就立馬從mAttachedScrap中取出剛被 detach的表項(xiàng)并重新attach它們。
pre-layout
LinearLayoutManager#onLayoutChildren
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
// 在填充表項(xiàng)之前會(huì)遍歷所有子表項(xiàng)土居,并逐個(gè)回收
detachAndScrapAttachedViews(recycler);
...
// 填充表項(xiàng)
fill()
}
post-layout
因?yàn)長ayoutManager中現(xiàn)有表項(xiàng)1枣购、2嬉探、3擦耀,所以scrap完成后,mAttachedScrap中存有表項(xiàng)1涩堤、2眷蜓、3的ViewHolder實(shí)例(position依次為0、0胎围、1吁系,被移除表項(xiàng)的position會(huì)被置0)。分別填充position位置為0和1的表項(xiàng)白魂。為2的位置緩存就不會(huì)命中汽纤。
緩存命中規(guī)則:position相同,并且表項(xiàng)沒被移除福荸。
為什么這么設(shè)計(jì):
為了確定動(dòng)畫的種類和起終點(diǎn)蕴坪,需要比對(duì)動(dòng)畫前和動(dòng)畫后的兩張“表項(xiàng)快照”,不然只知道最終位置不知道起始位置敬锐。
為了獲得兩張快照背传,就得布局兩次,分別是預(yù)布局和后布局(布局即是往列表中填充表項(xiàng))台夺,
為了讓兩次布局互不影響径玖,就不得不在每次布局前先清除上一次布局的內(nèi)容(就好比先清除畫布,重新作畫)颤介,
但是兩次布局中所需的某些表項(xiàng)大概率是一摸一樣的梳星,若在清除畫布時(shí)赞赖,把表項(xiàng)的所有信息都一并清除,那重新作畫時(shí)就會(huì)花費(fèi)更多時(shí)間(重新創(chuàng)建 ViewHolder 并綁定數(shù)據(jù))冤灾,
RecyclerView 采取了用空間換時(shí)間的做法:在清除畫布時(shí)把表項(xiàng)緩存在scrap結(jié)構(gòu)中薯定,以便在填充表項(xiàng)可以命中緩存,以縮短填充表項(xiàng)耗時(shí)瞳购。
整體總結(jié)
- Recycler有4個(gè)層次用于緩存ViewHolder對(duì)象话侄,優(yōu)先級(jí)從高到底依次為
ArrayList<ViewHolder> mAttachedScrap
肉渴、ArrayList<ViewHolder> mCachedViews
杨赤、ViewCacheExtension mViewCacheExtension
、RecycledViewPool mRecyclerPool
呐籽。如果四層緩存都未命中盏浇,則重新創(chuàng)建并綁定ViewHolder對(duì)象变丧。 - 緩存性能:
都不需要重新創(chuàng)建ViewHolder,只有RecycledViewPool绢掰,mChangedScrap需要重新綁定數(shù)據(jù)痒蓬。 - 緩存容量:
mAttachedScrap:沒有大小限制,但最多包含屏幕可見表項(xiàng)滴劲。
mCachedViews:默認(rèn)大小限制為2攻晒,放不下時(shí),按照先進(jìn)先出原則將最先進(jìn)入的ViewHolder存入回收池以騰出空間班挖。
mRecyclerPool:對(duì)ViewHolder按viewType分類存儲(chǔ)(通過SparseArray)鲁捏,同類ViewHolder存儲(chǔ)在默認(rèn)大小為5的ArrayList中。 - 緩存用途:
mAttachedScrap:用于布局過程中屏幕可見表項(xiàng)的回收和復(fù)用萧芙。
mCachedViews:用于移出屏幕表項(xiàng)的回收和復(fù)用给梅,且只能用于指定位置的表項(xiàng),有點(diǎn)像“回收池預(yù)備隊(duì)列”双揪,即總是先回收到mCachedViews动羽,當(dāng)它放不下的時(shí)候,按照先進(jìn)先出原則將最先進(jìn)入的ViewHolder存入回收池渔期。
mRecyclerPool:用于移出屏幕表項(xiàng)的回收和復(fù)用运吓,且只能用于指定viewType的表項(xiàng) - 緩存結(jié)構(gòu):
mAttachedScrap:ArrayList<ViewHolder>
mCachedViews:ArrayList<ViewHolder>
mRecyclerPool:對(duì)ViewHolder按viewType分類存儲(chǔ)在SparseArray<ScrapData>
中,同類ViewHolder存儲(chǔ)在ScrapData中的ArrayList中
參考
RecyclerView 源碼分析2-緩存機(jī)制圖解
RecyclerView 面試題 | 哪些情況下表項(xiàng)會(huì)被回收到緩存池擎场?