最近了解了一下RecycleView的緩存機(jī)制芽腾,做了一些記錄往弓,防止遺忘
一疏唾、RecyleView四級(jí)緩存
首先明確RecyecleView中緩存的對(duì)象是ViewHolder.
Recycler負(fù)責(zé)管理和緩存所有的ViewHolder。
RecycleView的緩存從上到下分為四層:scrap函似、cache槐脏、ViewCacheExtension、RecycleViewPool
1.1撇寞、 scrap
scrap 用來(lái)緩存正在顯示的ViewHolder顿天。
scrap 分為兩個(gè)集合:mAttachedScrap 和 mChangedScrap。
- mAttachedScrap 用于緩存正在顯示的ViewHolder
- mChangedScrap 用于緩存屏幕上發(fā)生變化的viewHolder,可能是數(shù)據(jù)發(fā)生了變化蔑担,也可能是ViewHolder類(lèi)型發(fā)生了變化牌废,mChangedScrap 中的ViewHolder會(huì)被移動(dòng)到RecycledViewPool中。
當(dāng)我們調(diào)用 notifyItemRangeChanged 方法的時(shí)候钟沛,會(huì)觸發(fā) requestLayout 方法畔规,就會(huì)重新布局,重新布局的話恨统,就會(huì)先將 viewHolder 放到 scrap 中(屏幕上變化的放入mChangedScrap 中叁扫,其余的放入mAttachedScrap 中)三妈,然后 fill 布局的時(shí)候,再?gòu)?mAttachedScrap 里面取出來(lái)直接使用莫绣。mChangedScrap 中的 viewHolder 會(huì)被移動(dòng)到 RecycledViewPool 中畴蒲,所以 mChangedScrap 對(duì)應(yīng)的 item 需要從 pool 中取對(duì)應(yīng)的 viewHolder,然后重新綁定对室。
View中的detach和remove
- detach 在ViewGroup中的實(shí)現(xiàn)很簡(jiǎn)單模燥,只是將當(dāng)前View從ParentView的ChildView數(shù)組中移除,將當(dāng)前View的mParent設(shè)置為null, 可以理解為輕量級(jí)的臨時(shí)remove掩宜。
- remove 代表真正的移除蔫骂,不光從ChildView數(shù)組中移除,其他和View樹(shù)各項(xiàng)聯(lián)系也會(huì)被徹底斬?cái)唷?/li>
Recycled View中的Scrap View
- Scrap View指的是在RecyclerView中牺汤,經(jīng)歷了detach操作的緩存辽旋。此類(lèi)緩存是通過(guò)position匹配的,不需要重新bindView檐迟。
- Recycled View指代的就是真正的移除操作remove后的緩存补胚,取出時(shí)需重新bindView使用。
1.2追迟、cached
數(shù)據(jù)結(jié)構(gòu)mCachedViews溶其,用于緩存從屏幕中移除,但是可能很快被再次顯示的ViewHolder
- 它是一個(gè) ArrayList 類(lèi)型,不區(qū)分 viewHolder 的類(lèi)型
- mCachedViews大小限制為2敦间,但是你可以使用 setItemViewCacheSize()這個(gè)方法調(diào)整它的大小瓶逃。
1.3、ViewCacheExtension
這個(gè)是需要自定義的每瞒,而且使用有很大的限制金闽,所以不深入介紹了。
1.4剿骨、RecycledViewPool
RecycledViewPool 儲(chǔ)存各個(gè)類(lèi)型的 viewHolder 它緩存的是被恢復(fù)出廠設(shè)置的viewHolder,需要重新調(diào)用bind 綁定數(shù)據(jù)代芜。
- RecycledViewPool 是按照ItemViewType 存儲(chǔ)ViewHolder的,每種ItemViewType最大數(shù)量為5
- 可以通過(guò) setMaxRecycledViews() 方法來(lái)設(shè)置每個(gè)類(lèi)型儲(chǔ)存的容量浓利。
- 針對(duì)RecycleView嵌套的場(chǎng)景挤庇,如一個(gè)縱向的RecycleView 嵌套橫向的RecycleView ,可以使用 setRecycledViewPool() 方法贷掖,公用RecycledViewPool
RecycleView滑出屏幕時(shí)的ViewHolder的復(fù)用過(guò)程
滾出屏幕的View會(huì)優(yōu)先保存到mCacheViews, 如果mCacheViews中保存滿了嫡秕,就會(huì)保存到RecyclerViewPool中。
- 檢查mCacheViews集合中是否還有空位苹威,如果有空位昆咽,則直接放到mCacheViews集合
- 如果沒(méi)有的話就把mCacheViews集合中最前面的ViewHolder拿出來(lái)放到RecyclerViewPool中,然后再把最新的這個(gè)ViewHolder放到mCacheViews集合
- 如果沒(méi)有成功緩存到mCacheViews集合中,就直接放到RecyclerViewPool
二掷酗、Recycler 緩存加載流程
- scrap負(fù)責(zé)緩存屏幕中正在顯示的ViewHolder调违,命中緩存后直接使用,不需要create和Bind
- 如果在 cache (mCachedViews)負(fù)責(zé)緩存剛剛移出屏幕泻轰,很可能被復(fù)用的ViewHolder技肩。通過(guò)position獲取,命中后不需create和bind
- ViewCacheExtension google預(yù)留的一個(gè)空的緩存,暫不討論
- pool (RecycledViewPool )根據(jù)ViewType緩存ViewHolder浮声, 用于緩存數(shù)據(jù)解綁后的ViewHolder虚婿,pool中命中的viewHolder 需要進(jìn)行重新bind 進(jìn)行數(shù)據(jù)綁定
- 如果所有緩存中都沒(méi)有命中 viewHolder,會(huì)重新調(diào)用createViewHolder 和 bindViewHolder
三泳挥、一些優(yōu)化方法
3.1然痊、 setHastFixedSize
當(dāng)知道Adapter內(nèi)Item的改變不會(huì)影響RecyclerView寬高的時(shí)候,可以設(shè)置為true讓RecyclerView避免重新計(jì)算大小羡洁。
注意兩點(diǎn):
- 當(dāng)調(diào)用Adapter的增刪改插方法玷过,最后就會(huì)根據(jù)mHasFixedSize這個(gè)值來(lái)判斷需要不需要requestLayout();
onItemRangeChanged(),
onItemRangeInserted(),
onItemRangeRemoved(),
onItemRangeMoved()
上面四個(gè)方法會(huì)調(diào)用triggerUpdateProcessor方法
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
會(huì)根據(jù)mHasFixedSize這個(gè)值來(lái)判斷需要不需要requestLayout()筑煮;
- 當(dāng)調(diào)用Adapter的notifyDataSetChanged() 最后調(diào)用了onChanged,調(diào)用了requestLayout()粤蝎,會(huì)去重新測(cè)量寬高真仲。
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
3.2、setHasStableIds
Adapter.setHasStablesId(true)開(kāi)啟固定ID
DemoAdapter mAdapter=new DemoAdapter();
mAdapter.setHasStablesId(true);
在 Adapter 類(lèi)中重寫(xiě)getItemId來(lái)給每個(gè) Item 一個(gè)唯一的ID初澎。
@Override
public long getItemId(int position){
return items.get(position).getId();
}
setHasStableIds(true)之后秸应,數(shù)據(jù)為發(fā)生變化情況下,滾動(dòng)recycleView
ViewHolder會(huì)被緩存到mAttachedScrap中,復(fù)用時(shí)通過(guò)position 從mAttachedScrap直接取出顯示碑宴,不需要重新createViewHolder软啼、bindViewHolder
用空間換時(shí)間,
從而規(guī)避滑動(dòng)recyelveView過(guò)程中出現(xiàn)的閃爍問(wèn)題。
3.3延柠、recycleView 圖片列表快速刷新
recyleView 中顯示圖片列表,快速滑動(dòng)容易出現(xiàn)卡頓祸挪。一個(gè)優(yōu)化思路,可以設(shè)置在滑動(dòng)過(guò)程中暫停正在加載的圖片,滑動(dòng)停止之后再恢復(fù)圖片的加載贞间。
recyceView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
Glide.with(requireActivity()).resumeRequests()
} else {
Glide.with(requireActivity()).pauseRequests()
}
}
})
參考文章:
http://www.reibang.com/p/1d2213f303fc
https://blog.csdn.net/weixin_43130724/article/details/90068112
http://www.reibang.com/p/4a2b18135447
https://zhuanlan.zhihu.com/p/80475040
http://www.reibang.com/p/aeb9ccf6a5a4
圖片閃爍問(wèn)題分析:
http://www.reibang.com/p/29352def27e6