1. RecyclerView緩存機制與性能優化關係
RecyclerView作性能優化要說複雜也複雜,好比說佈局優化,緩存,預加載等等。其優化的點不少,在這些看似獨立的點之間,其實存在一個樞紐:Adapter。由於全部的ViewHolder的建立和內容的綁定都須要通過Adaper的兩個函數onCreateViewHolder和onBindViewHolder
。android
所以咱們性能優化的本質就是要減小這兩個函數的調用時間和調用的次數
。若是咱們想對RecyclerView作性能優化,必須清楚的瞭解到咱們的每一步操做背後,onCreateViewHolder和onBindViewHolder調用了多少次。所以,瞭解RecyclerView的緩存機制是RecyclerView性能優化的基礎。緩存
爲了理解緩存的應用場景,本文首先會簡單介紹一下RecyclerView的繪製原理,而後再分析其緩存實現原理。性能優化
2. 繪製原理簡述
2.1 假設
爲了簡化問題,繪製原理介紹提供如下假設:bash
RecyclerView
以LinearLayoutManager爲例
忽略ItemDecoration
忽略ItemAnimator
忽略Measure過程
假設RecyclerView的width和height是肯定的
Recycler
2.2 繪製過程
(1)類的職責介紹函數
LayoutManager:接管RecyclerView的Measure,Layout,Draw的過程佈局
Recycler:緩存池性能
Adapter:ViewHolder的生成器和內容綁定器。fetch
(2)繪製過程簡介優化
RecyclerView.requestLayout開始發生繪製,忽略Measure的過程
在Layout的過程會經過LayoutManager.fill去將RecyclerView填滿
LayoutManager.fill會調用LayoutManager.layoutChunk去生成一個具體的ViewHolder
而後LayoutManager就會調用Recycler.getViewForPosition向Recycler去要ViewHolder
Recycler首先去一級緩存(Cache)裏面查找是否命中,若是命中直接返回。若是一級緩存沒有找到,則去三級緩存查找,若是三級緩存找到了則調用Adapter.bindViewHolder來綁定內容,而後返回。若是三級緩存沒有找到,那麼就經過Adapter.createViewHolder建立一個ViewHolder,而後調用Adapter.bindViewHolder綁定其內容,而後返回爲Recycler。【參見後文:2. 緩存機制】
一直重複步驟3-5,知道建立的ViewHolder填滿了整個RecyclerView爲止。
3. 緩存機制
3.1 源碼簡析
RecyclerView在Recyler裏面實現ViewHolder的緩存,Recycler裏面的實現緩存的主要包含如下5個對象:atom
ArrayList mAttachedScrap:未與RecyclerView分離的ViewHolder列表,若是仍依賴於 RecyclerView (好比已經滑動出可視範圍,但尚未被移除掉),但已經被標記移除的 ItemView 集合會被添加到 mAttachedScrap 中
按照id和position來查找ViewHolder
ArrayList mChangedScrap:表示數據已經改變的viewHolder列表,存儲 notifXXX 方法時須要改變的 ViewHolder,匹配機制按照position和id進行匹配
ArrayList mCachedViews:緩存ViewHolder,主要用於解決RecyclerView滑動抖動時的狀況,還有用於保存Prefetch的ViewHoder
最大的數量爲:mViewCacheMax = mRequestedCacheMax + extraCache(extraCache是由prefetch的時候計算出來的)
ViewCacheExtension mViewCacheExtension:開發者可自定義的一層緩存,是虛擬類ViewCacheExtension的一個實例,開發者可實現方法getViewForPositionAndType(Recycler recycler, int position, int type)來實現本身的緩存。
mRecyclerPool ViewHolder緩存池,在有限的mCachedViews中若是存不下ViewHolder時,就會把ViewHolder存入RecyclerViewPool中。
按照Type來查找ViewHolder
每一個Type默認最多緩存5個
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
複製代碼
3.2 緩存機制圖解
RecyclerView在設計的時候講上述5個緩存對象分爲了3級。每次建立ViewHolder的時候,會按照優先級依次查詢緩存建立ViewHolder。每次講ViewHolder緩存到Recycler緩存的時候,也會按照優先級依次緩存進去
。三級緩存分別是:
一級緩存:返回佈局和內容都都有效的ViewHolder
按照position或者id進行匹配
命中一級緩存無需onCreateViewHolder和onBindViewHolder
mAttachScrap在adapter.notifyXxx的時候用到
mChanedScarp在每次View繪製的時候用到,由於getViewHolderForPosition非調用屢次,後面將
mCachedView:用來解決滑動抖動的狀況,默認值爲2
二級緩存:返回View
按照position和type進行匹配
直接返回View
須要本身繼承ViewCacheExtension實現
位置固定,內容不發生改變的狀況,好比說Header若是內容固定,就可使用
三級緩存:返回佈局有效,內容無效的ViewHolder
按照type進行匹配,每一個type緩存值默認=5
layout是有效的,可是內容是無效的
多個RecycleView可共享
,可用於多個RecyclerView的優化
3.3 實例講解
實例解釋:
(1)因爲ViewCacheExtension在實際使用的時候較少用到,所以本例中忽略二級緩存
(2)mChangedScrap和mAttchScrap是RecyclerView內部控制的緩存,本例暫時忽略。
(3)爲了簡化問題,暫時不考慮PreFetch的狀況
(4)圖片解釋:
RecyclerView包含三部分:已經出屏幕,在屏幕裏面,即將進入屏幕,咱們滑動的方向是向上
RecyclerView包含三種Type:1,2,3。屏幕裏面的都是Type=3
紅色的線表明已經出屏幕的ViewHoder與Recycler的交互狀況
綠色的線表明,即將進入屏幕的ViewHoder進入屏幕時候,ViewHolder與Recycler的交互狀況
紫色的線表明mCacheView進入緩存池的狀況
出屏幕時候的狀況-mCacheViews未滿
當ViewHolder(position=0,type=1)出屏幕的時候,因爲mCacheViews是空的,那麼就直接放在mCacheViews裏面(從0-N是由老到新)。此時ViewHolder在mCacheViews裏面佈局和內容都是有效的,所以能夠直接複用。
ViewHolder(position=1,type=2)同步驟1
出屏幕時候的狀況-mCacheViews已經滿
當ViewHolder(position=2,type=1)出屏幕的時候因爲一級緩存mCacheViews已經滿了,所以而後移除mCacheViews裏面最老的ViewHolder(position=0,type=1)到RecyclePool中,而後將ViewHolder(position=2,type=1)存入mCacheViews。此時被移除到RecyclePool的ViewHolder的內容會被標記爲無效,當其複用的時候須要再次經過Adapter.bindViewHolder來綁定內容。
ViewHolder(position=3,type=3)同步驟3
進屏幕時候的狀況
當ViewHolder(position=3,type=3)進入屏幕繪製的時候,因爲Recycler的mCacheViews裏面找不到position匹配的View,同時RecyclerPool裏面找不到type匹配的View,所以,其只能經過adapter.createViewHolder來建立ViewHolder,而後經過adapter.bindViewHolder來綁定內容。
當ViewHolder(position=11,type=1)進入屏幕的時候,發現ReccylerPool裏面能找到type=1的緩存,所以直接從ReccylerPool裏面取來使用。因爲內容是無效的,所以還須要調用bindViewHolder來綁定佈局。同時ViewHolder(position=4,type=3)須要出屏幕,會經歷步驟3回收的過程
ViewHolder(position=12,type=3)同步驟6
屏幕往下拉ViewHoder(position=1)進入屏幕的狀況
因爲mCacheView裏面的有position=1的ViewHolder與之匹配,直接返回。因爲內容是有效的,所以無需再次綁定內容
ViewHolder(position=0)同步驟8
4. RecyclerView性能優化方向總結