RecyclerView 是用于大量數(shù)據(jù)展示的控件,相對(duì)于傳統(tǒng)的 ListView 欠雌,更加強(qiáng)大和靈活。
緩存機(jī)制
RecyclerView 與 ListView 的緩存機(jī)制原理大致相似疙筹, 滑動(dòng)的時(shí)候富俄,離屏的 ItemView 被回收至緩存禁炒,入屏的 ItemView 則會(huì)優(yōu)先從緩存中獲取,只是 ListView 與 RecyclerView 的實(shí)現(xiàn)細(xì)節(jié)有差異霍比。
ListView 緩存機(jī)制
ListView 主要是二級(jí)緩存幕袱,緩存的對(duì)象是 View,ListView 是繼承于 AbsListView 的悠瞬,而 AbsListView 里面有個(gè) mRecycler,用于存儲(chǔ)不使用的 view浅妆,其將被下次 layout 的時(shí)候重新使用望迎,以避免創(chuàng)建新的實(shí)例擂煞。
/**
* The data set used to store unused views that should be reused during the next layout
* to avoid creating new ones
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769398)
final RecycleBin mRecycler = new RecycleBin();
RecycleBin 是 AbsListView 的內(nèi)部類,其作用是通過(guò)兩級(jí)緩存來(lái)緩存 view趴乡。(RecycleBin 在 layout 的過(guò)程中便于 view 重用,RecycleBin 有兩級(jí)緩存:mActiveViews 和 mScrapViews)晾捏。
- mActiveViews
第一級(jí)緩存,這些 View 是布局過(guò)程開始時(shí)屏幕上的 view惦辛,layout 開始時(shí)這個(gè)數(shù)組被填充,layout 結(jié)束胖齐,mActiveViews 中的 View 移動(dòng)到 mScrapView,意義在于快速重用屏幕上可見的列表項(xiàng) ItemView呀伙,而不需要重新 createView 和 bindView补履。 - mScrapView
第二級(jí)緩存箫锤,mScrapView 是多個(gè) List 組成的數(shù)據(jù),數(shù)組的長(zhǎng)度為 viewTypeCount雨女,每個(gè) List 緩存不同類型 Item 布局的 View,其意義在于緩存離開屏幕的 ItemView氛堕,目的是讓即將進(jìn)入屏幕的 itemView 重用,當(dāng) mAdapter 被更換時(shí)讼稚,mScrapViews 則被清空浪耘。
RecyclerView 緩存機(jī)制
同樣地,RecyclerView 也有一個(gè)類專門來(lái)管理緩存塑崖,不過(guò)與 ListView 不同的是七冲,RecylerView 緩存的是 ViewHolder,而且實(shí)現(xiàn)的是四級(jí)緩存规婆,如下:
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
private ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
- mAttachedScrap
第一級(jí)緩存澜躺,相當(dāng)于 ListView 的 mActiveView,快速重用屏幕上可見的 ViewHolder抒蚜。 - mCacheViews
第二級(jí)緩存掘鄙,如果仍依賴于 RecyclerView(比如已經(jīng)滑出可視范圍,但還沒有被移除掉)嗡髓,但已經(jīng)被標(biāo)記移除的 ItemView 集合被添加到 mAttachedScrap 中操漠。然后如果 mAttachedScrap 中不再依賴時(shí)會(huì)被加入到 mCachedViews 中,默認(rèn)緩存 2 個(gè) ItemView饿这,RecycleView 從這里獲取的緩存時(shí)浊伙,如果數(shù)據(jù)源不變的情況下,無(wú)需重新 bindView长捧。 - mViewCacheExtension
第三級(jí)緩存嚣鄙,其是一個(gè)抽象靜態(tài)類,用于充當(dāng)附加的緩存池串结,當(dāng) RecyclerView 從 mCacheViews 找不到需要的 View 時(shí)哑子,將會(huì)從 ViewCacheExtension 中尋找。不過(guò)這個(gè)緩存是由開發(fā)者維護(hù)的肌割,如果沒有設(shè)置它卧蜓,則不會(huì)啟用。通常我們也不會(huì)設(shè)置它把敞,除非有特殊需求弥奸,比如要在調(diào)用系統(tǒng)的緩存池之前,返回一個(gè)特定的視圖先巴,才會(huì)用到它其爵。 - RecycledViewPool
第四級(jí)緩存冒冬,最強(qiáng)大的緩存器伸蚯,代碼如下:
public static class RecycledViewPool {
// 根據(jù) viewType 保存的被廢棄的 ViewHolder 集合,以便下次使用
private SparseArray<ArrayList<ViewHolder>> mScrap = new SparseArray<ArrayList<ViewHolder>>();
/**
* 從緩存池移除并返回一個(gè) ViewHolder
*/
public ViewHolder getRecycledView(int viewType) {
final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
if (scrapHeap != null && !scrapHeap.isEmpty()) {
final int index = scrapHeap.size() - 1;
final ViewHolder scrap = scrapHeap.get(index);
scrapHeap.remove(index);
return scrap;
}
return null;
}
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapHeapForType(viewType);
if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
return;
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
/**
* 根據(jù) viewType 獲取對(duì)應(yīng)緩存池
*/
private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
ArrayList<ViewHolder> scrap = mScrap.get(viewType);
if (scrap == null) {
scrap = new ArrayList<>();
mScrap.put(viewType, scrap);
if (mMaxScrap.indexOfKey(viewType) < 0) {
mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
}
}
return scrap;
}
}
顧名思義简烤,它是一個(gè)緩存池剂邮,實(shí)現(xiàn)上,是通過(guò)一個(gè)默認(rèn)為 5 大小的 ArrayList 實(shí)現(xiàn)的横侦。這一點(diǎn)挥萌,同 ListView 的 RecyclerBin 這個(gè)類一樣绰姻。每一個(gè) ArrayList 又都是放在一個(gè) Map 里面的,SparseArray 用兩個(gè)數(shù)組用來(lái)替代 Map引瀑。
把所有的 ArrayList 放在一個(gè) Map 里面狂芋,這也是 RecyclerView 最大的亮點(diǎn),這樣根據(jù) itemType 來(lái)取不同的緩存 Holder憨栽,每一個(gè) Holder 都有對(duì)應(yīng)的緩存帜矾,而只需要為這些不同 RecyclerView 設(shè)置同一個(gè) Pool 就可以了。
這個(gè)可以在 Pool 的 setRecycledViewPool() 方法可以看到注釋:
/**
* Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
* This can be useful if you have multiple RecyclerViews with adapters that use the same
* view types, for example if you have several data sets with the same kinds of item views
* displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
*
* @param pool Pool to set. If this parameter is null a new pool will be created and used.
*/
public void setRecycledViewPool(RecycledViewPool pool) {
mRecycler.setRecycledViewPool(pool);
}
RecyclerView 優(yōu)化
- 數(shù)據(jù)處理和視頻加載分離
耗時(shí)的數(shù)據(jù)處理邏輯應(yīng)該放在異步處理屡萤,這樣 Adapter 在 notify 改變數(shù)據(jù)時(shí)死陆,ViewHolder 可以操作數(shù)據(jù)于視圖的綁定邏輯措译。比如:
mTextView.setText(Html.fromHtml(data).toString());
這里的 Html.fromHtml(data) 方法可能就是比較耗時(shí)的饰序,存在多個(gè) TextView 的話耗時(shí)會(huì)更為嚴(yán)重,這樣便會(huì)引發(fā)掉幀掠械、卡頓注祖,故此時(shí)應(yīng)該在子線程處理。
數(shù)據(jù)優(yōu)化
分頁(yè)拉取數(shù)據(jù)時(shí)肚菠,對(duì)拉取下來(lái)的遠(yuǎn)端數(shù)據(jù)進(jìn)行緩存蚊逢,提升二次加載速度箫章;對(duì)于新增或者刪除數(shù)據(jù)通過(guò) DiffUtil 來(lái)進(jìn)行局部刷新數(shù)據(jù),而不是一味地全局刷新數(shù)據(jù)终抽。減少布局層級(jí)和過(guò)渡繪制
可以通過(guò)自定義 View 或者更合理地設(shè)置布局來(lái)減少層級(jí)昼伴,移除不必要的背景減少過(guò)度繪制。減少 xml 文件 inflate 時(shí)間
這里的 xml 文件不僅包括 layout 的 xml价涝,還包括 drawable 的 xml持舆,xml 文件 inflate 出 ItemView 時(shí)通過(guò)耗時(shí)的 IO 操作,尤其當(dāng) Item 的復(fù)用幾率很低的情況下泞遗,對(duì)著 Type 的增多席覆,這種 inflate 帶來(lái)的損耗時(shí)相當(dāng)大的,此時(shí)我們可以用代碼去生成布局聊倔。減少 View 對(duì)象的創(chuàng)建
一個(gè)稍微復(fù)雜的 Item 會(huì)包含大量的 View耙蔑,而大量的 View 的創(chuàng)建也會(huì)消耗大量時(shí)間,所以要盡可能簡(jiǎn)化 ItemView甸陌;設(shè)計(jì) ItemType 時(shí)盐股,對(duì)多 ViewType 能夠共用的部分盡量設(shè)計(jì)成自定義 View,減少 View 的構(gòu)造和嵌套牲尺。使用 RecyclerView 的 prefetch 功能
如果 Item 高度是固定的話幌蚊,可以使用 RecylerView.setHasFixedSize(true),來(lái)避免 requestLayout 浪費(fèi)資源蜒简。
滑動(dòng)過(guò)程沖停止數(shù)據(jù)加載或者圖片加載工作沫换。
如果不需要?jiǎng)赢嫞涯J(rèn)動(dòng)畫關(guān)閉來(lái)提升效率讯赏,動(dòng)畫在 Android 系統(tǒng)中是一個(gè)很大的開銷漱挎。
通過(guò) RecyclerView.setItemViewCacheSize(size);來(lái)加大 RecyclerView 的緩存私爷,用空間換時(shí)間來(lái)提高滾動(dòng)的流暢性膊夹。
如果多個(gè) RecyclerView 的 Adapter 是一樣的,比如嵌套的 RecyclerView 存在一樣的 Adapter工秩,可以通過(guò)設(shè)置 RecyclerView.setRecycledViewPool() 方法來(lái)共用一個(gè) RecyledViewPool进统。
通過(guò) getExtraLayoutSpace() 方法來(lái)增加 RecyclerView 預(yù)留的額外空間(顯示范圍之外,應(yīng)額外緩存空間)眉菱,如下:
new LinearLayoutManager(this) {
@Override
protected int getExtraLayoutSpace(RecyclerView.State state) {
return size;
}
};
- 在 onBindView 的時(shí)候只做數(shù)據(jù)綁定數(shù)據(jù)工作掉分,不要?jiǎng)?chuàng)建對(duì)象。