DEVLOG 9.15 RecyclerView復(fù)用和緩存機(jī)制源碼分析
我是花了一天左右的時(shí)間實(shí)現(xiàn)了一個(gè)手寫(xiě)的比較簡(jiǎn)單的RecyclerView之后開(kāi)始詳細(xì)地看RecyclerView的緩存復(fù)用機(jī)制。個(gè)人覺(jué)得手寫(xiě)一個(gè)RecyclerView對(duì)于緩存復(fù)用的理解會(huì)比較有效,我也把在實(shí)現(xiàn)過(guò)程中遇到的問(wèn)題總結(jié)在這里手寫(xiě)RecyclerView的問(wèn)題總結(jié)和分析

當(dāng)我們手指觸摸到由RecyclerView渲染的ItemView的列表時(shí),不論是向上還是向下滑動(dòng),都會(huì)有View出界并且有View加入,這個(gè)過(guò)程肯定由ViewGroup的onTouchEvent處理,所以我們理所應(yīng)當(dāng)先看看RecyclerView#onTouchEvent中的復(fù)用和緩存的實(shí)現(xiàn):

RecyclerView#onTouchEvent:
我們直接開(kāi)始進(jìn)入onTouchEvent的ACTION_MOVE的事件分析。在手指進(jìn)行位移的事件中,onTouchEvent調(diào)用了scrollByInternal方法。類(lèi)似于我們?cè)谑謱?xiě)RecyclerView中的那樣(這部分的內(nèi)容在我這篇筆記中有分析,這里自己實(shí)現(xiàn)一個(gè)scroll操作也是為了避免ViewGroup的scroll對(duì)于畫(huà)布的操作,畢竟我們需要模擬item劃出去的操作。
果不其然,ViewGroup#scrollBy方法也被重寫(xiě)了,并且調(diào)用了scrollByInternal。所以我們?cè)敿?xì)看看scrollByInternal和后續(xù)的實(shí)現(xiàn):
?LayoutManager#scrollStep
我們需要明確我們現(xiàn)在看源碼的關(guān)鍵點(diǎn),關(guān)鍵點(diǎn)在于滑動(dòng)處理和復(fù)用機(jī)制,而在RecyclerView#scrollStep中需要我們關(guān)注的和緩存復(fù)用機(jī)制有關(guān)的方法就是scrollStep。
在scrollStep中,通過(guò)mLayout(其實(shí)上是一個(gè)LayoutManager對(duì)象的實(shí)例),實(shí)現(xiàn)了水平和垂直滑動(dòng),在RecyclerView中默認(rèn)的實(shí)現(xiàn)返回的都是0:
當(dāng)然,因?yàn)樵赗ecyclerView中定義的LayoutManager是一個(gè)抽象類(lèi),具體的布局邏輯實(shí)際上交給了LayoutManager的子類(lèi)實(shí)現(xiàn):
我們可以以LinearLayoutManager為例看看垂直和橫向滑動(dòng)的邏輯。

# LinearLayoutManager#scrollVerticallly
LinearLayoutManager在使用上需要指定滑動(dòng)的方向。LinearLayoutManager在默認(rèn)情況下使用的是垂直(VERTICAL)參數(shù)。不過(guò)不論如何都會(huì)調(diào)用scrollBy。LinearLayout 重寫(xiě)了scrollBy方法。在scrollBy方法以及后續(xù)的調(diào)用中,Recycler這個(gè)類(lèi)會(huì)作為RecyclerView復(fù)用View機(jī)制的實(shí)現(xiàn)。所以我們需要重點(diǎn)看看Recycler中對(duì)于View復(fù)用的原理。?
以上的代碼調(diào)用鏈我歸納到了這張圖中:

RecyclerView的復(fù)用機(jī)制: 概括

RecyclerView的緩存和復(fù)用機(jī)制兩者是密不可分的,假設(shè)我們現(xiàn)在的場(chǎng)景是需要從已經(jīng)緩存好的【容器】中尋找可用的ViewHolder,根據(jù)上圖的概括,這種緩存機(jī)制可以分為四層。
相關(guān)函數(shù)都是在RecyclerView#tryGetViewHolderForPositionByDeadline中調(diào)用的。這try方法會(huì)依次調(diào)用如圖所示的方法來(lái)產(chǎn)生holder,如果holder為空就會(huì)到下一個(gè)方法中再產(chǎn)生。下面稍微概括性的說(shuō)說(shuō)每一個(gè)方法關(guān)聯(lián)的角色和作用。
Recycler#getChangedScrapViewForPosition?
這個(gè)方法從mChangedScrap中尋找可以使用的ViewHolder對(duì)象。
Recycler#getScrapOrHiddenOrCachedHolderForPosition
這個(gè)方法和下面的ForId方法都會(huì)從mAttachScrap和mCachedViews中尋找可用的ViewHolder對(duì)象,這兩個(gè)對(duì)象都是ArrayList<ViewHolder>。這里需要注意的是mChangedScrap和mAttachScrap都是存儲(chǔ)了當(dāng)前仍附屬在ViewGroup上的ViewHolder,這點(diǎn)可以從Recycler#scrapView的注釋中得到。視頻中說(shuō)這兩個(gè)對(duì)象是存儲(chǔ)仍然在屏幕中的ViewHolder,通過(guò)手寫(xiě)RecyclerView的經(jīng)驗(yàn)來(lái)分析,這樣說(shuō)好像有點(diǎn)道理,但是我沒(méi)有特別詳細(xì)的去看源碼,所以這點(diǎn)存疑,姑且按照他說(shuō)的來(lái)理解。
所以根據(jù)這樣的劃分依據(jù),我們可以將mChangedScrap和mAttachedScrap作為一級(jí)緩存,mCachedViews作為二級(jí)緩存。
ViewCacheExtension
這個(gè)內(nèi)容是由開(kāi)發(fā)者自己定義的。
Adapter#createViewHolder
當(dāng)以上的緩存方式中都無(wú)法提供合適的ViewHolder時(shí),會(huì)使用Adapter創(chuàng)建合適的ViewHolder。
RecyclerView的緩存機(jī)制
緩存機(jī)制的入口應(yīng)該分成布局和滑動(dòng),先看看布局這塊的緩存處理:
LinearLayoutManager#onLayoutChildren
在LinearLayoutManager布局的過(guò)程中會(huì)考慮ViewHolder的緩存問(wèn)題,具體的緩存過(guò)程的實(shí)現(xiàn)主要需要看定義在LayoutManager中的srapOrRecycleView方法。

具體的方法調(diào)用可以參考這個(gè)流程圖,我們現(xiàn)在目光主要首先聚焦到LayoutManager#scrapOrRecycleView身上:
以上的代碼中的兩個(gè)分支都和Recycler的實(shí)例有關(guān)。這兩個(gè)分支的邏輯概括起來(lái)都是緩存ViewHolder的操作。我們首先看看第一個(gè)分支中的recycleViewHolderInternal:
Recycler#recycleViewHolderInternal:
在對(duì)于緩存的ViewHolder進(jìn)行緩存的過(guò)程中,Recycler會(huì)檢查mCachedView的大小,如果超過(guò)出設(shè)定的大?。?),就會(huì)移除mCachedViews中的第一個(gè)元素,并且將這個(gè)移除之后的ViewHolder放置在緩存池中。這個(gè)操作的實(shí)現(xiàn)是由Recycler#recycleCachedViewAt實(shí)現(xiàn)的:
Recycler#recycleCachedViewAt
如圖所示,結(jié)合上面的代碼的邏輯,如果需要mCachedView放不下新的ViewHolder,就會(huì)移除第一個(gè)ViewHolder,并且將新的ViewHolder放在mCachedView后面,同時(shí),被移除的ViewHolder1會(huì)被加入緩存池RecycledViewHolderPool中:

RecycledViewHolderPool是一個(gè)內(nèi)部類(lèi),可以簡(jiǎn)單看看他的代碼:
當(dāng)具體要使用RecycledViewPool存儲(chǔ)ViewHolder時(shí),在putRecycledView中會(huì)判斷RecycledViewPool的緩存池(具體是mScrap)是否存儲(chǔ)滿(mǎn),存儲(chǔ)滿(mǎn)就不要這個(gè)ViewHolder,直接放棄;沒(méi)有的話就會(huì)將這個(gè)ViewHolder通過(guò)不同的ViewType添加到對(duì)應(yīng)的mScrapHeap中:
再看看這張圖,ViewHolder的緩存就比較容易理解了。

所以和布局相關(guān)的緩存機(jī)制如圖所示。在布局過(guò)程中,左邊的邏輯會(huì)緩存劃出屏幕外的View,右邊會(huì)緩存仍然在屏幕內(nèi)的View:

LinearLayout#fill
如果是在滑動(dòng)過(guò)程中,緩存的邏輯如圖所示。滑動(dòng)主要處理在屏幕外的View,而不用考慮在屏幕內(nèi)的View,所以沒(méi)有上圖右邊的邏輯。

**參考內(nèi)容:**
- [RecyclerView復(fù)用機(jī)制](https://www.bilibili.com/video/BV1Dp4y1t7kn?from=search&seid=6466487146831843251&spm_id_from=333.337.0.0)