RecyclerView的使用場景非常豐富葛峻,而本篇的源碼分析基于上下滑動(dòng)一個(gè)列表的場景來觀察它的復(fù)用-回收機(jī)制剪返。本文基于27.0.0版本進(jìn)行分析踩萎,如下是Demo展示:
RecyclerView繼承自ViewGroup棋蚌,屬于系統(tǒng)級別的自定義控件岛抄,而它的源碼長達(dá)12000多行,還不包括抽取出去的其他輔助類月培、管理類等嘁字,可想而知其復(fù)雜性,本文的分析思路主要是集中在RecyclerView的緩存機(jī)制上杉畜,通過滑動(dòng)事件結(jié)合源碼分析它的復(fù)用-回收機(jī)制纪蜒,而RecyclerView的繪制流程、ItemDecoration此叠、LayoutManager纯续、State、Recycler等會(huì)一筆帶過灭袁。
自定義控件三部曲:onMeasure - onLayout - onDraw猬错,RecyclerView也不例外。查看源碼可以看到简卧,RecyclerView測量的一部分邏輯委托給了LayoutManager兔魂,源碼如下所示,進(jìn)來判斷是否存在LayoutManager實(shí)例举娩,不存在則調(diào)用defaultOnMeasure進(jìn)行默認(rèn)測量。然后就是一個(gè)if...else...判斷是否為AutoMeasure构罗,LinearLayoutManager和GridLayoutManager使用這種模式铜涉,而StaggerLayoutManager在一定條件下會(huì)使用自定義測量這種模式。
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
//LinearLayoutManager和GridLayoutManager使用這種模式
if (mLayout.mAutoMeasure) {
...
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
...
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
...
//而StaggerLayoutManager在一定條件下會(huì)使用自定義測量這種模式
}
}
測量之后會(huì)執(zhí)行onLayout遂唧,這里我們分析采用垂直布局的LinearLayoutManager芙代,在布局的邏輯中會(huì)經(jīng)過如下三個(gè)方法:dispatchLayoutStep1 - dispatchLayoutStep2 - dispatchLayoutStep3,它們各司其職盖彭。
dispatchLayoutStep1:處理Adapter的更新和動(dòng)畫相關(guān)
dispatchLayoutStep2:真正執(zhí)行LayoutManager.onLayoutChildren纹烹,該函數(shù)的實(shí)現(xiàn)決定了ChildView將會(huì)怎樣被布局(layout)
dispatchLayoutStep3:保存動(dòng)畫相關(guān)的信息并做必要的清理工作
所以我們重點(diǎn)放到LayoutManager.onLayoutChildren上页滚,直接進(jìn)入LinearLayoutManager的onLayoutChildren,發(fā)現(xiàn)代碼很長铺呵,里面也有注釋信息裹驰,布局的邏輯如下:1 首先尋找錨點(diǎn),2 從錨點(diǎn)開始片挂,底部向上填充幻林,頂部向下填充,3 如果再有剩余空間音念,再填充一次沪饺。下面的LinearLayoutManager配合垂直布局的onLayout代碼段:
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
ensureLayoutState();
mLayoutState.mRecycle = false;
// 確定布局方向
resolveShouldLayoutReverse();
// 尋找錨點(diǎn)
final View focused = getFocusedChild();
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// 計(jì)算錨點(diǎn)的位置和坐標(biāo)
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
}
...
detachAndScrapAttachedViews(recycler);//回收view
// 下面是LinearLayoutManager配合垂直布局的代碼
// 先向下繪制
updateLayoutStateToFillEnd(mAnchorInfo);
// 填充view
fill(recycler, mLayoutState, state, false);
...
// 再向上繪制
updateLayoutStateToFillStart(mAnchorInfo);
// 填充view
fill(recycler, mLayoutState, state, false);
//還有可用空間
if (mLayoutState.mAvailable > 0) {
...
// 再次填充view
fill(recycler, mLayoutState, state, false);
}
...
}
至此我們大致了解了布局的算法邏輯:先找錨點(diǎn)再多次不同方向上進(jìn)行填充,而RecyclerView的復(fù)用流程和回收流程都在該方法里面發(fā)起闷愤,所以onLayout是我們分析緩存機(jī)制的入口整葡。其中復(fù)用流程是fill,回收流程是detachAndScrapAttachedViews讥脐。到這里我們先總結(jié)下onMeasure和onLayout的內(nèi)容:
1.RecyclerView是將繪制流程交給LayoutManager處理掘宪,如果沒有設(shè)置不會(huì)測量子View
2.繪制流程是區(qū)分正向和倒置的
3.繪制是先確定錨點(diǎn),然后再多次不同方向上進(jìn)行填充攘烛,fill()至少會(huì)執(zhí)行兩次魏滚,如果繪制完還有剩余空間,則會(huì)再執(zhí)行一次fill()方法
4.LayoutManager獲得View(也可理解為復(fù)用入口)是從RecyclerView中的onLayout開始的(fill)坟漱,涉及到RecyclerView的緩存策略鼠次,如果沒有拿到緩存,則走我們自己重寫的onCreateView方法芋齿,再調(diào)用onBindViewHolder
5.LayoutManager回收View的入口也是RecyclerView中的onLayout開始的(detachAndScrapAttachedViews)腥寇,涉及到RecyclerView的緩存策略
下面就會(huì)詳細(xì)分析復(fù)用流程和回收流程,這里先確定流程的入口是onLayout方法觅捆。onDraw的代碼這里不再進(jìn)行分析赦役。這里根據(jù)源碼的執(zhí)行順序會(huì)先進(jìn)行回收再復(fù)用,所以下面先分析回收流程栅炒。
1. 回收流程
回收流程的入口方法是 LinearLayoutManager - onLayoutChildren - detachAndScrapAttachedViews -scrapOrRecycleView掂摔,最后一個(gè)方法名翻譯一下是:廢棄或者回收view,在該方法中會(huì)根據(jù)一定的策略來決定是scrap還是recycle赢赊,下面是scrapOrRecycleView的源碼:
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
//從指定的view中獲取到對應(yīng)的viewHolder
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
//viewHolder已經(jīng)無效乙漓,并且還沒有被remove,并且沒有指定的stableId
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
//remove該項(xiàng)
removeViewAt(index);
//通過recycler執(zhí)行內(nèi)部回收流程释移,主要是將viewHolder放到RecycledViewPool中
recycler.recycleViewHolderInternal(viewHolder);
} else {
//detach該項(xiàng)
detachViewAt(index);
//通過recycler將view從scrap數(shù)組中移除
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
上面的代碼中牽出了兩個(gè)比較重要的概念:remove和detach叭披。
detach: 在ViewGroup中的實(shí)現(xiàn)很簡單,只是將ChildView從ParentView的ChildView數(shù)組中移除玩讳,ChildView的mParent設(shè)置為null涩蜘,可以理解為輕量級的臨時(shí)remove嚼贡,因?yàn)閂iew此時(shí)和View樹還是藕斷絲連,這個(gè)函數(shù)被經(jīng)常用來改變ChildView在ChildView數(shù)組中的次序同诫。View被detach一般是臨時(shí)的粤策,在后面會(huì)被重新attach。
remove: 真正的移除剩辟,不光被從ChildView數(shù)組中除名掐场,其他和View樹各項(xiàng)聯(lián)系也會(huì)被徹底斬?cái)?不考慮Animation/LayoutTransition這種特殊情況),比如焦點(diǎn)被清除贩猎,從TouchTarget中被移除等熊户。
所以我們可以將scrapOrRecycleView對應(yīng)起來:scrap-detach,recycler-remove吭服。同時(shí)滿足下面的3個(gè)條件會(huì)被recycler嚷堡,其余情況下viewHolder都會(huì)被scrap:
1、viewHolder本身已經(jīng)完全無效
2艇棕、viewHolder對應(yīng)的項(xiàng)還沒有被remove(這個(gè)判斷是考慮到預(yù)加載的原因蝌戒,先不具體說)
3、adapter沒有指定stableId沼琉,因?yàn)槿绻付ū惫叮筒淮嬖赩iew綁定內(nèi)容無效的可能了
Demo案例實(shí)操過程中,上下滑動(dòng)時(shí)基本上都是觸發(fā)recycler打瘪;當(dāng)插入一個(gè)元素或者刪除一個(gè)元素友鼻,或者本質(zhì)上說調(diào)用notifyDataSetChanged后,就會(huì)觸發(fā)scrap闺骚。
下面再看看recycler執(zhí)行內(nèi)部回收流程彩扔,大致邏輯是先判斷viewHolder的一些標(biāo)志位,達(dá)到回收條件后僻爽,先將viewHolder緩存到mCachedViews中虫碉,如果mCachedViews已滿,則刪除mCachedViews中最老的一個(gè)元素胸梆,并將該元素放到RecycledViewPool中敦捧;再接著將本次要回收的元素放到mCachedViews中。 如果未達(dá)到條件乳绕,則直接將viewHolder放到RecycledViewPool中绞惦。下面這段代碼是整理之后的源碼,描述了上述邏輯:
void recycleViewHolderInternal(ViewHolder holder) {
//進(jìn)行必要的校驗(yàn)洋措,否則拋出異常
if (holder.isScrap() || holder.itemView.getParent() != null) {
throw new IllegalArgumentException(
"Scrapped or attached views may not be recycled. isScrap:"
+ holder.isScrap() + " isAttached:"
+ (holder.itemView.getParent() != null) + exceptionLabel());
}
//進(jìn)行必要的校驗(yàn),否則拋出異常
if (holder.isTmpDetached()) {
throw new IllegalArgumentException("Tmp detached view should be removed "
+ "from RecyclerView before it can be recycled: " + holder
+ exceptionLabel());
}
//進(jìn)行必要的校驗(yàn)杰刽,否則拋出異常
if (holder.shouldIgnore()) {
throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
+ " should first call stopIgnoringView(view) before calling recycle."
+ exceptionLabel());
}
...
if (forceRecycle || holder.isRecyclable()) {
//有效條件檢查
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
int cachedViewSize = mCachedViews.size();
//判斷cachedViewSize是否大于最大緩存數(shù)量
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
//回收最老的元素菠发,即第0號元素
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
//計(jì)算出緩存元素的index值
targetCacheIndex = cacheIndex + 1;
}
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
//未到達(dá)條件王滤,又沒被緩存,則直接放到RecycledViewPool
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
...
}
根據(jù)上面的源碼滓鸠,我們debug的方法調(diào)用路徑是:recycleViewHolderInternal - recycleCachedViewAt - addViewHolderToRecycledViewPool雁乡。下面就是addViewHolderToRecycledViewPool中最關(guān)鍵的源碼,將元素放到RecycledViewPool中糜俗,可以看到這里區(qū)分了type踱稍,每個(gè)type對應(yīng)一個(gè)ArrayList,同時(shí)進(jìn)入到這里的viewHolder會(huì)被重置悠抹,主要是重置position以及flags珠月。
public void putRecycledView(ViewHolder scrap) {
//拿到type
final int viewType = scrap.getItemViewType();
//拿到type對應(yīng)的ViewHolder集合
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");
}
//重置viewHolder
scrap.resetInternal();
//將viewHolder添加到集合中
scrapHeap.add(scrap);
}
看完recycler的流程之后,還有一種回收場景scrap楔敌,scrap的場景中會(huì)涉及到比較多的全局變量啤挎,如mChildHelper,mAttachedScrap卵凑,mChangedScrap庆聘。首先mAttachedScrap和mChangedScrap都是ArrayList類型的緩存viewHolder變量的。mChildHelper是ChildHelper的實(shí)例對象勺卢,RecyclerView盡管本身是一個(gè)ViewGroup伙判,但是將ChildView管理職責(zé)全權(quán)委托給了ChildHelper,所有關(guān)于ChildView的操作都要通過ChildHelper來間接進(jìn)行黑忱,ChildHelper成為了一個(gè)ChildView操作的中間層宴抚,getChildCount/getChildAt等函數(shù)經(jīng)由ChildHelper的攔截處理再下發(fā)給RecyclerView的對應(yīng)函數(shù),其參數(shù)或者返回結(jié)果會(huì)根據(jù)實(shí)際的ChildView信息進(jìn)行改寫杨何。了解了基本的概念之后酱塔,看看scrapOrRecycleView中的detach分支,下面是detach分支的關(guān)鍵源碼:
//detach下標(biāo)為index的view
detachViewAt(index);
//在recycler中維護(hù)下這個(gè)scrapView
recycler.scrapView(view);
detachViewAt中是通過mChildHelper處理view和parentView的關(guān)系危虱;而在scrapView中羊娃,則通過判斷viewHolder是否被removed,是否invalid埃跷,是否canReuseUpdatedViewHolder條件來決定是放到mAttachedScrap中還是mChangedScrap中蕊玷。源碼如下所示:
void scrapView(View view) {
//拿到viewHolder
final ViewHolder holder = getChildViewHolderInt(view);
//是否被removed,或者invalid弥雹,或者canReuseUpdatedViewHolder
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
...
holder.setScrapContainer(this, false);
//放到mAttachedScrap中
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
//否則放到mChangedScrap中
mChangedScrap.add(holder);
}
}
這里還需要對比下remove和scrap在復(fù)用性上的不同垃帅,只被detach的View要比被remove的View高,detach的View一般來說代表可以直接復(fù)用(其ViewHolder對應(yīng)于Data的Position還是有效的剪勿,只需要重新綁定數(shù)據(jù)贸诚,如果數(shù)據(jù)也沒變化的話,甚至都不用重新綁定數(shù)據(jù);View還是有效的酱固,View綁定的數(shù)據(jù)可能有效的, 比如一個(gè)列表有N項(xiàng)械念,現(xiàn)在刪除了其中一項(xiàng),那么在沒有其他變化的前提下运悲,剩余的N-1個(gè)項(xiàng)對應(yīng)的ViewHolder是可以直接復(fù)用的)龄减,這一點(diǎn)非常關(guān)鍵,避免了不必要的綁定(和ListView等相比)班眯,項(xiàng)處理的粒度從整體細(xì)化到了單個(gè)項(xiàng)希停,即包含了對View的復(fù)用,也包含了對View當(dāng)前綁定內(nèi)容的復(fù)用署隘。被remove的View復(fù)用性上則要差一些宠能,其對應(yīng)的Position已經(jīng)無效,這種復(fù)用層級和Scrap相比只有View層級的復(fù)用(稍帶可以復(fù)用ViewHolder,只不過里面的信息要重新設(shè)置定踱,但起碼不用new一個(gè))棍潘。
至此回收機(jī)制的流程基本完成,回顧一下崖媚,首先在RecyclerView的onLayout方法中會(huì)在dispatchLayoutStep2中將布局的權(quán)利移交給LayoutManger亦歉,Demo中對應(yīng)就是LinearLayoutManager。LinearLayoutManager接管之后畅哑,調(diào)用自身的onLayoutChildren肴楷,然后就會(huì)對view進(jìn)行回收(detachAndScrapAttachedViews)和填充(fill)。detachAndScrapAttachedViews中會(huì)根據(jù)一定的條件決定該view是被scrap(對應(yīng)detach)還是被recycler(對應(yīng)remove)荠呐。被recycler的view會(huì)先經(jīng)過mCachedViews再根據(jù)條件進(jìn)入到RecyclerViewPool中赛蔫,而被scrap的元素會(huì)根據(jù)具體條件看是放到mAttachedScrap還是mChangedScrap中緩存起來。
2. 復(fù)用流程
上面根據(jù)LinearLayoutManager的onLayoutChildren中代碼的執(zhí)行順序泥张,先分析了回收機(jī)制的流程呵恢,接下來繼續(xù)分析復(fù)用機(jī)制的流程,還是遵循上文的思路媚创,先確定入口方法渗钉,再確定一條方法調(diào)用流程,然后再細(xì)細(xì)分析钞钙。上文提到過LinearLayoutManager配合垂直布局的onLayout代碼段鳄橘,找到錨點(diǎn),先向下繪制-再填充-再向上繪制-再填充的流程芒炼,這里的fill方法便是我們分析復(fù)用機(jī)制的入口方法了瘫怜。
// 下面是LinearLayoutManager配合垂直布局的代碼
// 先向下繪制
updateLayoutStateToFillEnd(mAnchorInfo);
// 填充view
fill(recycler, mLayoutState, state, false);
...
// 再向上繪制
updateLayoutStateToFillStart(mAnchorInfo);
// 填充view
fill(recycler, mLayoutState, state, false);
//還有可用空間
if (mLayoutState.mAvailable > 0) {
...
// 再次填充view
fill(recycler, mLayoutState, state, false);
}
進(jìn)入fill后,會(huì)根據(jù)layoutState是否還有更多項(xiàng)要填充本刽,來循環(huán)調(diào)用layoutChunk方法鲸湃,根據(jù)layoutChunk這個(gè)方法名猜測其作用就是布局塊用的赠涮,一塊一塊對應(yīng)就是一項(xiàng)一項(xiàng)的item。在layoutChunk中唤锉,先通過next方法找到view世囊,然后對該view進(jìn)行再測量和布局别瞭,以及邊框的確定窿祥。這篇文章的重點(diǎn)是關(guān)注緩存機(jī)制,所以繪制布局這塊一筆帶過蝙寨,我們將重點(diǎn)放到next方法上晒衩。下面是next方法的源碼:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
//通過recycler對象找到一個(gè)合適的view
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
上述代碼最關(guān)鍵的一句是recycler.getViewForPosition(mCurrentPosition),通過給定的position獲取一個(gè)view的實(shí)例對象墙歪,最終會(huì)通過tryGetViewHolderForPositionByDeadline方法得到一個(gè)viewHolder听系,再通過viewHolder里面的itemView屬性將view實(shí)例對象返回。如下源碼所示:
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
//先獲取viewHolder虹菲,再通過itemView屬性得到view的實(shí)例
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
Recycler一般不會(huì)直接作用于View靠胜,其操作的對象一般是ViewHolder。如果你有自己debug代碼毕源,留意了view和viewHolder之間的關(guān)系浪漠,你會(huì)發(fā)現(xiàn)它們之間是雙向綁定的,view中持有viewHolder是通過LayoutParams的mViewHolder屬性霎褐;而viewHolder中持有view是通過itemView屬性址愿。在tryGetViewHolderForPositionByDeadline中總的思路是,依次經(jīng)過RecyclerView中的四級緩存冻璃,一級一級找响谓,找到了就返回viewHolder,沒有的話省艳,就回調(diào)用戶的onCreateViewHolder和onBindViewHolder娘纷。RecyclerView中的四級緩存更細(xì)致的說應(yīng)該是Recycler中的四級緩存,分別是:mAttachedScrap - mCachedViews - mViewCacheExtension - mRecyclerPool跋炕。
mAttachedScrap:對應(yīng)上述回收機(jī)制中的Scrap View赖晶,保存在mAttachedScrap或者mChangedScrap中,用于屏幕內(nèi)的itemView快速復(fù)用枣购。
mCachedViews:對應(yīng)上述回收機(jī)制中的remove view嬉探,默認(rèn)上線為2個(gè)。
mViewCacheExtension:供使用者自行擴(kuò)展棉圈,讓使用者可以控制緩存涩堤。
mRecyclerPool:對應(yīng)于上述回收機(jī)制中remove view放到mCachedViews后溢出的view,同時(shí)可以用與RecyclerView之間共享ViewHolder的緩存池分瘾。
了解了上面四級緩存后胎围,接著看tryGetViewHolderForPositionByDeadline的代碼會(huì)輕松很多,如下源碼:
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
ViewHolder holder = null;
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
//從scrap或hidden或cache中找viewHolder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
//檢查找到的holder是不是能夠被當(dāng)前的位置使用,不行的話就要對該viewHolder進(jìn)行回收
if (!validateViewHolderForOffsetPosition(holder)) {
// dryRun一般為false白魂,表示可以從scrap或者cache中移除
if (!dryRun) {
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
//執(zhí)行內(nèi)部回收邏輯
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
...
//獲取type
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
//有設(shè)置stableId汽纤,則嘗試從scrap或者cache中獲取
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
//判斷是否設(shè)置了外部擴(kuò)展
if (holder == null && mViewCacheExtension != null) {
// 從外部擴(kuò)展中找
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
...
}
}
if (holder == null) { // fallback to pool
...
//根據(jù)tyep從RecycledViewPool中找
holder = getRecycledViewPool().getRecycledView(type);
...
}
if (holder == null) {
//回調(diào)用戶的onCreateViewHolder方法
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;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
...
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//回調(diào)用戶的onBindViewHolder方法
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
//獲取LayoutParams
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
//轉(zhuǎn)成RecyclerView所需類型的LayoutParams
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
//將viewHolder保存到mViewHolder屬性中
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
return holder;
}
上述源碼我對其進(jìn)行了刪減,保留了核心流程代碼福荸,從上倒下就是從四級緩存中逐個(gè)查找蕴坪,實(shí)在沒有則創(chuàng)建一個(gè),最后將viewHolder綁定到view上敬锐,完成最后的雙向綁定背传。
至此復(fù)用機(jī)制的流程基本完成,總結(jié)一下台夺,方法的調(diào)用流程是:LinearLayoutManager - onLayoutChildren - fill() - layoutChunk() - layoutState.next() - getViewForPosition() - tryGetViewHolderForPositionByDeadline() - 四級緩存 or onCreateViewHolder - onBindViewHolder(未綁定的情況下會(huì)觸發(fā)綁定回調(diào))径玖。這套流程下來要關(guān)注兩個(gè)地方,一個(gè)是fill方法颤介,它會(huì)被調(diào)用多次梳星;一個(gè)是tryGetViewHolderForPositionByDeadline方面,里面涉及到RecyclerView復(fù)用機(jī)制的核心邏輯:四級緩存滚朵。
3. RecyclerView的優(yōu)勢
3.1. RecyclerView與ListView對比
RecyclerView強(qiáng)制使用ViewHolder冤灾,當(dāng)然在使用ListView的時(shí)候都是自定義ViewHolder配合使用,避免每次createView時(shí)調(diào)用findViewById始绍。但是RecyclerView在ViewHolder基礎(chǔ)上定義了很多flag標(biāo)識表明當(dāng)前ViewHolder的可用性狀態(tài)瞳购,這點(diǎn)比ListView中自定義ViewHolder要更加豐富。
在處理離屏緩存這一場景時(shí)亏推,RecyclerView與ListView的處理也有很大的不同学赛。RecyclerView會(huì)從mCachedViews中獲取到一個(gè)viewHolder,然后會(huì)判斷這個(gè)viewHolder是否已被綁定吞杭,是否不需要更新盏浇,是否有效,如果滿足其中任何一個(gè)條件就不會(huì)觸發(fā)onBindViewHolder芽狗。源碼如下所示:
//處理預(yù)加載的情況
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {//如果已被綁定绢掰,或者不需要更新或者是有效的,就不會(huì)觸發(fā)tryBindViewHolderByDeadline方法了
...
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
而ListView的處理則是mRecycler得到一個(gè)緩存的view童擎,然后重新getView滴劲,此處勢必會(huì)調(diào)用onBind觸發(fā)重新綁定的邏輯。AbsListView源碼如下所示:
View obtainView(int position, boolean[] outMetadata) {
...
//拿到緩存的view
final View scrapView = mRecycler.getScrapView(position);
//每次都調(diào)用getView顾复,也就意味著每次都調(diào)用onBind
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
if (child != scrapView) {
// Failed to re-bind the data, return scrap to the heap.
mRecycler.addScrapView(scrapView, position);
} else if (child.isTemporarilyDetached()) {
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView().
child.dispatchFinishTemporaryDetach();
}
}
...
}
下面的gif動(dòng)圖展示了RecyclerView中處理離屏緩存時(shí)班挖,onBind方法的執(zhí)行情況,當(dāng)用戶輕微的來回滑入滑出item時(shí)芯砸,此時(shí)是從mCachedViews中拿到緩存的viewHolder直接復(fù)用萧芙,不會(huì)觸發(fā)onBind操作给梅。
3.2. 局部刷新功能
處理局部刷新時(shí),ListView是一鍋端双揪,將所有的mActiveViews都移入了二級緩存mScrapViews动羽,而RecyclerView則是更加靈活地對每個(gè)View修改標(biāo)志位,區(qū)分是否重新bindView渔期。通過局部刷新能避免調(diào)用許多無用的bindView运吓,下面的gif動(dòng)圖展示了局部刷新position位置為4的場景,我們可以觀察第二個(gè)透明框中的onBind的情況擎场。
參考:
RecyclerView機(jī)制分析: Recycler
Android ListView與RecyclerView對比淺析--緩存機(jī)制