Android踩坑經驗--RecycleView Adapter緩存問題

項目中遇到一個問題,先看現象:
在這裏插入圖片描述
發現在一個屏幕上,會出現顯示格式不一致問題,比較詭異,同一個佈局文件,爲什麼繪製出來顯示效果不一樣呢?
RecycleView item的顯示,主要是onCreateViewHolder和onBindViewHolder兩個方法,看下方法調用情況:
在這裏插入圖片描述
發現在滑動的時候,並沒有執行onCreateViewHolder,而是執行的onBindViewHolder,只有最後有一個item是“沒有更多了 ”執行了onCreateViewHolder,那麼滑動的時候應該是取的RecycleView的緩存。
但爲什麼緩存會出現時間的寬度不對的問題呢?
看下佈局文件,發現:
在這裏插入圖片描述大膽猜測:如果寬度設置爲0dp,子view沒有重新measure,繪製時viewholder從緩存中獲取,依然沿用了緩存中的width,而緩存中的width與此次onBindViewHolder中的數據長度不吻合,這才導致了上述情況。於是嘗試將寬度設置爲wrap_content,發現問題解決了。
RecycleView使用總結
在使用RecycleView時,需要對緩存有比較清晰的認識,最好能恢復View原來的屬性,否則會出現混亂的情況。
在網上瀏覽時,也有類似case,比如holder有ImageView,點擊後做屬性動畫,然後被RecycleView緩存起來,別的Item又拿來複用,雖然設置了圖片,但ImageView已經做過屬性動畫,比如已經處於180旋轉的狀態,這時候顯示就會有問題。
旋轉之類的動畫,可以用View動畫,複雜的動畫再用屬性動畫避免此問題,也可以重寫Adapter的void onViewDetachedFromWindow(VH holder)方法,在裏面拿到holder找到修改過的ImageView,恢復他原來的屬性,避免出現顯示混亂的問題
RecycleView緩存機制
疑問:複用的item取的是緩存?哪裏的緩存?
答案:緩存來自RecycledViewPool,是RecycleView的四級緩存
RecyclerView的緩存機制,大致有3個類:Recycler、RecycledViewPool和ViewCacheExtension。
Recycler
用於管理已經廢棄或者與RecyclerView分離的ViewHolder,爲了方便理解這個類,整理了下面的資料,內部類的成員變量和他們的含義:
在這裏插入圖片描述
RecycledViewPool
RecycledViewPool類是用來緩存Item用,是一個ViewHolder的緩存池,如果多個RecyclerView之間用setRecycledViewPool(RecycledViewPool)設置同一個RecycledViewPool,他們就可以共享Item。其實RecycledViewPool的內部維護了一個Map,裏面以不同的viewType爲Key存儲了各自對應的ViewHolder集合。可以通過提供的方法來修改內部緩存的Viewholder。
ViewCacheExtension
開發者可自定義的一層緩存,是虛擬類ViewCacheExtension的一個實例,開發者可實現方法getViewForPositionAndType(Recycler recycler, int position, int type)來實現自己的緩存。
Recyclerview的四級緩存
1.屏幕內緩存
屏幕內緩存指在屏幕中顯示的ViewHolder,這些ViewHolder會緩存在mAttachedScrap、mChangedScrap中 :
mChangedScrap 表示數據已經改變的ViewHolder列表
mAttachedScrap 未與RecyclerView分離的ViewHolder列表
2.屏幕外緩存
當列表滑動出了屏幕時,ViewHolder會被緩存在 mCachedViews ,其大小由mViewCacheMax決定,默認DEFAULT_CACHE_SIZE爲2,可通過Recyclerview.setItemViewCacheSize()動態設置。
3.自定義緩存
可以自己實現ViewCacheExtension類實現自定義緩存,可通過Recyclerview.setViewCacheExtension()設置。
4.緩存池
ViewHolder首先會緩存在 mCachedViews 中,當超過了個數(比如默認爲2), 就會添加到 RecycledViewPool 中。RecycledViewPool 會根據每個ViewType把ViewHolder分別存儲在不同的列表中,每個ViewType最多緩存DEFAULT_MAX_SCRAP = 5 個ViewHolder,如果RecycledViewPool沒有被多個RecycledView共享,對於線性佈局,每個ViewType最多隻有一個緩存,如果是網格有多少行就緩存多少個。他們之間的關係如下 :
在這裏插入圖片描述
緩存策略
Recyclerview在獲取ViewHolder時按四級緩存的順序查找,如果沒找到就創建。其中只有RecycledViewPool找到時纔會調用 bindViewHolder,其它緩存不會重新bindViewHolder 。 流程如下 :
(從源碼中看,感覺最開始會先從mChangedScrap查詢(getChangedScrapViewForPosition方法),如果未找到再去mAttachedScrap,mCachedViews中查詢(getScrapOrHiddenOrCachedHolderForPosition方法),建議還是閱讀Android源碼,在getViewForPosition()方法中基本就可知道緩存的運行機制,大致調用棧:getViewForPosition-> getChangedScrapViewForPosition()->getScrapOrHiddenOrCachedHolderForPosition->getViewForPositionAndType()->getRecycledViewPool().getRecycledView(type))
在這裏插入圖片描述
通過了解RecyclerView的四級緩存,我們可以知道,RecyclerView最多可以緩存 N(屏幕最多可顯示的item數) + 2 (屏幕外的緩存) + 5*M (M代表M個ViewType,緩存池的緩存),只有RecycledViewPool找到時纔會重新調用 bindViewHolder。
還需要注意的是,RecycledViewPool 可以被多個RecyclerView共享,其緩存個數與ViewType個數、佈局相關,如果RecycledViewPool沒有被多個RecycledView共享,對於線性佈局,每個ViewType最多隻有一個緩存,如果是網格佈局有多少行就緩存多少個。

參考博客:https://zhooker.github.io/2017/08/14/關於Recyclerview的緩存機制的理解/
RecycleView稍有不慎,會有性能問題,關於性能優化有一幅圖留作備忘:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章