RecyclerView緩存原理

RecyclerView緩存機制

1. RecyclerView緩存機制與性能優化關係

RecyclerView做性能優化要說複雜也複雜,比如說佈局優化,緩存,預加載等等。其優化的點很多,在這些看似獨立的點之間,其實存在一個樞紐:Adapter。因爲所有的ViewHolder的創建和內容的綁定都需要經過Adaper的兩個函數onCreateViewHolder和onBindViewHolder

因此我們性能優化的本質就是要減少這兩個函數的調用時間和調用的次數。如果我們想對RecyclerView做性能優化,必須清楚的瞭解到我們的每一步操作背後,onCreateViewHolder和onBindViewHolder調用了多少次。因此,瞭解RecyclerView的緩存機制是RecyclerView性能優化的基礎。

爲了理解緩存的應用場景,本文首先會簡單介紹一下RecyclerView的繪製原理,然後再分析其緩存實現原理。

這裏寫圖片描述

2. 繪製原理簡述

2.1 假設

爲了簡化問題,繪製原理介紹提供以下假設:

  • RecyclerView
    • 以LinearLayoutManager爲例
    • 忽略ItemDecoration
    • 忽略ItemAnimator
    • 忽略Measure過程
    • 假設RecyclerView的width和height是確定的
  • Recycler
    • 忽略mViewCacheExtension

2.2 繪製過程

(1)類的職責介紹

LayoutManager:接管RecyclerView的Measure,Layout,Draw的過程

Recycler:緩存池

Adapter:ViewHolder的生成器和內容綁定器。

(2)繪製過程簡介

  1. RecyclerView.requestLayout開始發生繪製,忽略Measure的過程
  2. 在Layout的過程會通過LayoutManager.fill去將RecyclerView填滿
  3. LayoutManager.fill會調用LayoutManager.layoutChunk去生成一個具體的ViewHolder
  4. 然後LayoutManager就會調用Recycler.getViewForPosition向Recycler去要ViewHolder
  5. Recycler首先去一級緩存(Cache)裏面查找是否命中,如果命中直接返回。如果一級緩存沒有找到,則去三級緩存查找,如果三級緩存找到了則調用Adapter.bindViewHolder來綁定內容,然後返回。如果三級緩存沒有找到,那麼就通過Adapter.createViewHolder創建一個ViewHolder,然後調用Adapter.bindViewHolder綁定其內容,然後返回爲Recycler。【參見後文:2. 緩存機制】
  6. 一直重複步驟3-5,知道創建的ViewHolder填滿了整個RecyclerView爲止。

3. 緩存機制

3.1 源碼簡析

RecyclerView在Recyler裏面實現ViewHolder的緩存,Recycler裏面的實現緩存的主要包含以下5個對象:

  • 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)爲了簡化問題,暫時不考慮Pretch的情況

(4)圖片解釋:

  • RecyclerView包含三部分:已經出屏幕,在屏幕裏面,即將進入屏幕,我們滑動的方向是向上
  • RecyclerView包含三種Type:1,2,3。屏幕裏面的都是Type=3
  • 紅色的線代表已經出屏幕的ViewHoder與Recycler的交互情況
  • 綠色的線代表,即將進入屏幕的ViewHoder進入屏幕時候,ViewHolder與Recycler的交互情況

出屏幕時候的情況

  1. 當ViewHolder(position=0,type=1)出屏幕的時候,由於mCacheViews是空的,那麼就直接放在mCacheViews裏面,ViewHolder在mCacheViews裏面佈局和內容都是有效的,因此可以直接複用。
  2. ViewHolder(position=1,type=2)同步驟1
  3. 當ViewHolder(position=2,type=1)出屏幕的時候由於一級緩存mCacheViews已經滿了,因此將其放入RecyclerPool(type=1)的緩存池裏面。此時ViewHolder的內容會被標記爲無效,當其複用的時候需要再次通過Adapter.bindViewHolder來綁定內容。
  4. ViewHolder(position=3,type=2)同步驟3

進屏幕時候的情況

  1. 當ViewHolder(position=3-10,type=3)進入屏幕繪製的時候,由於Recycler的mCacheViews裏面找不到position匹配的View,同時RecyclerPool裏面找不到type匹配的View,因此,其只能通過adapter.createViewHolder來創建ViewHolder,然後通過adapter.bindViewHolder來綁定內容。
  2. 當ViewHolder(position=11,type=1)進入屏幕的時候,發現ReccylerPool裏面能找到type=1的緩存,因此直接從ReccylerPool裏面取來使用。由於內容是無效的,因此還需要調用bindViewHolder來綁定佈局。同時ViewHolder(position=4,type=3)需要出屏幕,其直接進入RecyclerPool(type=3)的緩存池中
  3. ViewHolder(position=12,type=2)同步驟6

屏幕往下拉ViewHoder(position=1)進入屏幕的情況

  1. 由於mCacheView裏面的有position=1的ViewHolder與之匹配,直接返回。由於內容是有效的,因此無需再次綁定內容
  2. ViewHolder(position=0)同步驟8

4. RecyclerView性能優化方向總結

這裏寫圖片描述

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