RecyclerView緩存復(fù)用機(jī)制
來到RecyclerView的Adapter代碼中:
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
Log.i("minfo", "onCreateViewHolder");
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
((TextView)holder.textView).setText(datas.get(position));
Log.i("minfo", "onBindViewHolder");
}
在onCreateViewHolder方法和onBindViewHolder方法打印关炼,不斷滑動(dòng)recyclerView盏浇,會(huì)發(fā)現(xiàn)一個(gè)現(xiàn)象就是剛開始都會(huì)調(diào)兩個(gè)方法,但是調(diào)用一定次數(shù)之后,就只會(huì)調(diào)onBindViewHolder方法了斩箫。那么這個(gè)滑動(dòng)過程中旨怠,就是在不斷地回收復(fù)用拷泽。
首先要明白夹姥,RecyclerView緩存復(fù)用的是什么?緩存復(fù)用的是ViewHolder婶溯,ViewHolder是itemView的封裝類鲸阔。
Recycler 是 RecyclerView 的內(nèi)部類,也是這套復(fù)用機(jī)制的核心迄委,顯然 Recycler 的主要成員變量也都是用來緩存和復(fù)用 ViewHolder 的:
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<>();
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
}
復(fù)用機(jī)制
在滑動(dòng)過程中褐筛,recyclerView不斷在復(fù)用ViewHolder來顯示item,那么就從recyclerView的滑動(dòng)事件入手源碼叙身,然后步步深入:
RecyclerView.onLayout(...)
-> RecyclerView.dispatchLayout()
-> RecyclerView.dispatchLayoutStep2() // do the actual layout of the views for the final state.
-> mLayout.onLayoutChildren(mRecycler, mState) // mLayout 類型為 LayoutManager
-> LinearLayoutManager.onLayoutChildren(...) // 以 LinearLayoutManager 為例
-> LinearLayoutManager.fill(...) // The magic functions :) 填充給定的布局渔扎,注釋很自信的說這個(gè)方法很獨(dú)立,稍微改動(dòng)就能作為幫助類的一個(gè)公開方法信轿,程序員的快樂就是這么樸實(shí)無華晃痴。
-> LinearLayoutManager.layoutChunk(recycler, layoutState) // 循環(huán)調(diào)用,每次調(diào)用填充一個(gè) ItemView 到 RV
-> LinearLayoutManager.LayoutState.next(recycler)
-> RecyclerView.Recycler.getViewForPosition(int) // 回到主角了,通過 Recycler 獲取指定位置的 ItemView
-> Recycler.getViewForPosition(int, boolean) // 調(diào)用下面方法獲取 ViewHolder,并返回上面需要的 viewHolder.itemView
-> Recycler.tryGetViewHolderForPositionByDeadline(...)
最終調(diào)用 tryGetViewHolderForPositionByDeadline方法來獲取ViewHolder财忽,這個(gè)方法里面是復(fù)用的核心倘核,方法部分代碼:
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
if (mState.isPreLayout()) {
// 從 mChangedScrap 里面去獲取 ViewHolder,動(dòng)畫相關(guān)
holder = getChangedScrapViewForPosition(position);
}
if (holder == null) {
// mAttachedScrap即彪、 mHiddenViews紧唱、mCachedViews 獲取 ViewHolder
// 這個(gè) mHiddenViews 是用來做動(dòng)畫期間的復(fù)用
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
final int type = mAdapter.getItemViewType(offsetPosition);
// 如果 Adapter 的 hasStableIds 方法返回為 true
// 優(yōu)先通過 ViewType 和 ItemId 兩個(gè)條件從 mAttachedScrap 和 mCachedViews 尋找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
}
if (holder == null && mViewCacheExtension != null) {
//從自定義緩存獲取
View view = mViewCacheExtension
getViewForPositionAndType(this, position, type);
holder = getChildViewHolder(view);
}
}
if (holder == null) {
//從 RecycledViewPool 獲取 ViewHolder
holder = getRecycledViewPool().getRecycledView(type);
}
if (holder == null) {
// 如果四級(jí)緩存里都沒有,那就再調(diào)用createViewHolder創(chuàng)建ViewHolder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
結(jié)合RecyclerView類中的源碼及注釋可知隶校,Recycler會(huì)依次從mChangedScrap/mAttachedScrap漏益、mCachedViews、mViewCacheExtension深胳、mRecyclerPool中嘗試獲取指定位置或ID的ViewHolder對(duì)象以供重用绰疤,如果全都獲取不到則直接重新創(chuàng)建。
緩存機(jī)制
總共有四級(jí)緩存舞终,按照優(yōu)先級(jí)分:
- 一級(jí)緩存:
一級(jí)緩存的代碼:
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
scrap是用來保存被rv移除掉但最近又馬上要使用的緩存轻庆,比如說rv中自帶item的動(dòng)畫效果癣猾。
mChangedScrap/mAttachedScrap
稍微仔細(xì)看的話就能發(fā)現(xiàn)scrap緩存有兩個(gè)成員mChangedScrap和mAttachedScrap,它們保存的對(duì)象有些不一樣余爆,一般調(diào)用adapter的notifyItemRangeChanged被移除的viewholder會(huì)保存到mChangedScrap煎谍,其余的notify系列方法(不包括notifyDataSetChanged)移除的viewholder會(huì)被保存到mAttachedScrap中。用來緩存還在屏幕內(nèi)的viewholder龙屉。
二級(jí)緩存:mCachedViews ,用來緩存移除屏幕之外的 ViewHolder满俗,默認(rèn)情況下緩存容量是 2转捕,可以通過 setViewCacheSize 方法來改變緩存的容量大小。如果 mCachedViews 的容量已滿唆垃,則會(huì)根據(jù) 先進(jìn)先出 的規(guī)則移除舊 ViewHolder五芝,再將viewHolder添加進(jìn)RecycledViewPool中。
三級(jí)緩存:ViewCacheExtension 辕万,開發(fā)給用戶的自定義擴(kuò)展緩存枢步,需要用戶自己管理 View 的創(chuàng)建和緩存。通過提供給開發(fā)者抽象方法來自己實(shí)現(xiàn)緩存渐尿。
四級(jí)緩存:RecycledViewPool 醉途,ViewHolder 緩存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 時(shí)砖茸,會(huì)從cachedView緩存中移除隘擎,然后將移除的 ViewHolder 存入RecyclerViewPool 中。
將緩存viewHolder添加到recyclerViewPool中凉夯,根據(jù)itemtype獲取對(duì)應(yīng)的ScrapData數(shù)據(jù)货葬,里面存儲(chǔ)著該itemType的緩存集合。RecycledViewPool默認(rèn)大小為5劲够,當(dāng)大于了最大容量震桶,就不再緩存。
可以通過以下方式修改RecycledViewPool的緩存大姓饕铩:
RecyclerView.getRecycledViewPool().setMaxRecycledViews(int viewType, int max);
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
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");
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
//根據(jù)itemtype獲取對(duì)應(yīng)的ScrapData數(shù)據(jù)
private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}
mCachedViews二級(jí)緩存與四級(jí)緩存RecycledViewPool的區(qū)別:
cacheView緩存的viewHolder帶有數(shù)據(jù)蹲姐,而四級(jí)緩存已經(jīng)清空了數(shù)據(jù),只是緩存了viewHolder炒瘸,從 RecyclerViewPool 中取出來的 ViewHolder 需要重新執(zhí)行 bindViewHolder才能使用淤堵。所以二級(jí)緩存的復(fù)用速度會(huì)比四級(jí)緩存的復(fù)用速度更高。
mCachedViews只會(huì)緩存2個(gè)viewholder顷扩,就是屏幕滑出去的上面一個(gè)item和下面一個(gè)item拐邪。當(dāng)上面一個(gè)item剛滑出去或者下面一個(gè)item剛滑出去,緩存在mCachedViews里面隘截,內(nèi)容不會(huì)被清空扎阶。此時(shí)再把這個(gè)item滑入屏幕內(nèi)汹胃,不會(huì)走onBindViewHolder,因?yàn)閿?shù)據(jù)還在东臀。而隨著往上或往下繼續(xù)滑動(dòng)更多着饥,移除屏幕的item超過2個(gè),mCachedViews緩存容量不足惰赋,就會(huì)將滑出屏幕的viewholder往recyclerViewPool中緩存宰掉。而把這些更多的item滑回屏幕時(shí),這些緩存的viewholder數(shù)據(jù)已經(jīng)被清掉赁濒,所以會(huì)調(diào)用onBindViewHolder方法重設(shè)數(shù)據(jù)
繪圖總結(jié):
參考:
RecyclerView 的緩存復(fù)用機(jī)制
https://www.bilibili.com/video/BV1Yb4y1R7xn?p=3&spm_id_from=pageDriver&vd_source=40c24e77b23dc2e50de2b7c87c6fed59