歡迎關注本人公衆號,掃描下方二維碼或搜索公衆號 id: mxszgggit
開題前,筆者仍是要說幾句先,依舊和前文同樣,文章內不涉及源碼講解,默認各位讀者對源碼有必定的瞭解,撰文的緣由也如同前文,由於筆者認爲當下在 ListView/RecyclerView 的源碼講解的文章中,大都是對着源碼噼裏啪啦,實在有些晦澀難懂,因而筆者想將部分數據可視化,手摸手帶領讀者去了解一下緩存機制的實現,另推薦閱讀騰訊 Bugly 的《Android ListView與RecyclerView對比淺析--緩存機制》一文。github
1.前文地址:《可視化 ListView 緩存機制,手摸手帶你打通任督二脈》算法
2.本文項目地址:RecyclerViewVisualization 或直接下載 apksegmentfault
但願閱讀本文前請先閱讀前文,本文所涉及的一些關鍵字在上文有所說起。數組
手摸手打開 app:緩存
RecyclerView 中的一緩 mAttachedScrap 與 ListView 中的一緩 mActiveViews 功能是基本類似的,爲了屏幕內 item 快速複用而存在(RecyclerView/ListView 具備兩次 onLayout() 過程,第二次 onLayout() 中直接使用第一次 onLayout() 緩存的 View,而沒必要再建立)。數據結構
實際上,二緩 mCachedViews 加上四緩 RecyclerViewPool 合在一塊兒與 ListView 的二緩 mScrapedViews 意義相同,爲了即將給即將入屏的 item 複用而存在。下面來細談下二緩:app
二緩是經過 position 來匹配相應的 ViewHolder 的,這裏的 position 指的是 RecyclerView 預測的、可能進入屏幕的 item 的 position,它是由當前屏幕滑動方向和可見的 item 位置來共同決定的。例如:屏幕向下滑動,那麼可能進入屏幕的 item 的 position 就是當前可見第一個 item 的 position - 1;屏幕向上滑動,那麼可能進入屏幕的 item 的 position 就是當前可見的最後一個 item 的 position + 1。這樣提及來可能有些模糊,舉個例子:post
以上述狀態來講,若是屏幕下滑,那麼預測下一個可能出如今屏幕上的 item 的 position 多是 4(也就是 Item1『E(layoutPosition:4)』);而若是屏幕上滑,預測的下一個出如今屏幕上的 item 的 position 是 0(也就是 Item『A(layoutPosition:0)』)。而後經過將 position 用於與 mCacheViews 中的 ViewHolder 的 layoutPosition 作比較,若是相同則返回該 ViewHolder。性能
來看動圖:
1.屏幕上滑:
能夠看到 target mCacheView position 由 0 變成了 4。與此同時,mCachedViews 將可能出如今屏幕上的 item 的位置從原有的位置調整爲 ArrayList
2.屏幕下滑:
屏幕下滑的話,target mCachedView position 由 4 變成了 0。與此同時,mCachedViews 內部也會作相應的調整。
四緩(RecycledViewPool)性質:
內部維護了一個 SparseArray
SparseArray key 爲 ViewHolder 的 ViewType,這說明每一套 ViewHolder 都具備本身的緩存數據
SparseArray value 爲 ScrapData 類型,ScrapData 就是關鍵的緩存數據了,其數據結構簡略以下:
static class ScrapData {
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = 5;
// ...
}
複製代碼
因而可知,針對每一種 ViewHolder,RecycledViewPool 都會維護一個默認大小爲 5 的 ArrayList
RecycledViewPool#setMaxRecycledViews(viewType, max)
來替換的,好比想換成大一點的 十、20,都是能夠的(這也是該數據類型爲 ArrayList
前面說到——「實際上,二緩 mCachedViews 加上四緩 RecyclerViewPool 合在一塊兒與 ListView 的二緩 mScrapedViews 意義相同,爲了即將給即將入屏的 item 複用而存在。」,可能有小夥伴疑惑了,既然意義相同,爲什麼不是隻有二緩就足夠了,還要多一個四緩來更復雜?原因在於:能夠由開發者主動向內填充數據(RecycledViewPool#putRecycledView(ViewHolder)
),技術上能夠實現多個 RecyclerView 共用同一個 RecyclerViewPool(RecyclerView#setRecycledViewPool(RecycledViewPool)
)。這兩點在筆者看來,是在某種業務場景下選擇 RecyclerView 仍是 ListView 的一個重要原因所在。至於這兩點的實踐,第一點筆者已經添加在 Demo 中了(含彩蛋),第二點筆者就不在此處擴展了,各位讀者能夠自行添加入 RecyclerViewVisualization Demo 中,相應的數據也都會被展現到屏幕上~
一個 View 被完整的展現到屏幕上,應該通過建立 View 和給 View 添加數據(BindView)兩個過程,因此實際上緩存機制不只僅針對於 View 要作緩存,最好還能對添加數據的這個過程再優化下,畢竟 setText()、setImage() 也多是一個耗時操做。RecyclerView 就針對此作了優化,咱們知道,ListView 實際上緩存的是 View,而 RecyclerView 實際上緩存的是 ViewHolder,這就意味着 ListView 雖然能夠複用 View,可是給 View 添加數據這個過程就不能複用了,而若是是複用 ViewHolder 的話,不只複用了 View,同時將給 View 添加數據的這個過程也被「緩存」起來了,而 RecyclerView 就是這麼幹的 ——
咱們能夠看到,但凡是被二緩緩存起來的 ViewHolder 再被展現到屏幕上,是不會觸發 BindViewHolder 這個過程的。
ps:固然,這得基於數據源不變,若是數據源改變,確定得從新給 View 添加數據。
1.局部刷新:
2.全局刷新:
能夠看到,局部刷新可以只針對改變的 View 進行 bind view,而全局刷新會針對被影響到的全部的 View 都進行 bind view。因此,在平常使用中,是否是應該多考慮使用局部刷新代替全局刷新呢?