RecyclerViewのリサイクル動作で使われるキャッシュは、サイズを大きくすれば多くのViewHolderが保持できます。その分、多くのメモリを消費します。
ViewHolderを多く保持できたとしても、サイクル動作で効率よく使われなければ、メモリの浪費です。
キャッシュのサイズはRecyclerViewの使われ方よって適切なサイズがあります。
そのため、RecyclerViewはキャッシュのサイズを変更できるようになっています。
CachedViewsのデフォルトサイズ
CachedViewsのサイズはRecyclerView.RecyclerクラスのmViewCacheMaxフィールドが持っています。
public final class Recycler { ... private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; int mViewCacheMax = DEFAULT_CACHE_SIZE; ... static final int DEFAULT_CACHE_SIZE = 2; ... public void setViewCacheSize(int viewCount) { mRequestedCacheMax = viewCount; updateViewCacheSize(); } void updateViewCacheSize() { int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0; mViewCacheMax = mRequestedCacheMax + extraCache; // first, try the views that can be recycled for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > mViewCacheMax; i--) { recycleCachedViewAt(i); } } ... }
mViewsCacheMaxはmRequestedCacheMax(初期値:2)とextraCacheの加算値です。
extraCacheの値はLayoutManager(抜粋中のmLayout)に依存します。列に並ぶViewHolderの数(orientation:VERTICALの時)がextraCacheに入ります。
よって、mViewsCacheMaxの値は表のようになります。
LayoutManager (spanCount) | ||||
Linear | Grid(2) | Grid(3) | Grid(4) | |
mRequestedCacheMax | 2 | 2 | 2 | 2 |
extraCache | 1 | 2 | 3 | 4 |
mViewCacheMax | 3 | 4 | 5 | 6 |
CachedViewsのサイズ変更
mViewsCacheMaxはmRequestedCacheMax(初期値:2)とextraCacheの加算値であることを先に述べました。
このmRequestedCacheMaxはRecyclerView.Recycler#setViewCacheSize( )で変更できるようになっています。
public final class Recycler { ... private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; int mViewCacheMax = DEFAULT_CACHE_SIZE; ... static final int DEFAULT_CACHE_SIZE = 2; ... public void setViewCacheSize(int viewCount) { mRequestedCacheMax = viewCount; updateViewCacheSize(); } void updateViewCacheSize() { int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0; mViewCacheMax = mRequestedCacheMax + extraCache; // first, try the views that can be recycled for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > mViewCacheMax; i--) { recycleCachedViewAt(i); } } ... }
... final Recycler mRecycler = new Recycler(); ... public void setItemViewCacheSize(int size) { mRecycler.setViewCacheSize(size); } ...
これにより、CachedViewsのサイズを変更できます。
CachedViewsの適切なサイズ
キャッシュのサイズを変更できることがわかったところで、最適なサイズを考察してみようと思います。
例えば、GridLayoutManagerでspanCount:3とし、スクロールした場合のリサイクル動作をあげます。
3列(extraCache:3)を同時にスクロールするのでキャッシュのサイズは5です。
... I/Adapter: View Attached item = Item 9 [pos=9,type=0]
... I/Adapter: View Attached item = Item A [pos=10,type=0]
... I/Adapter: View Attached item = Item B [pos=11,type=0]
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter: Bind ViewHolder!! position = 12
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter: Bind ViewHolder!! position = 13
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter: Bind ViewHolder!! position = 14
... I/Adapter: View Detached item = Item 2 [pos=2,type=0]
... I/Adapter: View Detached item = Item 1 [pos=1,type=0]
... I/Adapter: View Detached item = Item 0 [pos=0,type=0]
... I/Adapter: View Recycled item = Item C [pos=12,type=0]
... I/Adapter: Bind ViewHolder!! position = 12
アイテム9/A/Bを表示した後に、次のアイテムC/D/Eをプリフェッチします。
... I/Adapter: View Attached item = Item 9 [pos=9,type=0]
... I/Adapter: View Attached item = Item A [pos=10,type=0]
... I/Adapter: View Attached item = Item B [pos=11,type=0]
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter: Bind ViewHolder!! position = 12
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter: Bind ViewHolder!! position = 13
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter: Bind ViewHolder!! position = 14
... I/Adapter: View Detached item = Item 2 [pos=2,type=0]
... I/Adapter: View Detached item = Item 1 [pos=1,type=0]
... I/Adapter: View Detached item = Item 0 [pos=0,type=0]
... I/Adapter: View Recycled item = Item C [pos=12,type=0]
... I/Adapter: Bind ViewHolder!! position = 12
スクロールが進むと、アイテム0/1/2が非表示なりCachedViewsに保持されます。しかし、空席が2つしかありません。そのためプリフェッチ中の1つが溢れてRecyclerPoolへ保持されます。
... I/Adapter: View Attached item = Item 9 [pos=9,type=0]
... I/Adapter: View Attached item = Item A [pos=10,type=0]
... I/Adapter: View Attached item = Item B [pos=11,type=0]
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter: Bind ViewHolder!! position = 12
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter: Bind ViewHolder!! position = 13
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter: Bind ViewHolder!! position = 14
... I/Adapter: View Detached item = Item 2 [pos=2,type=0]
... I/Adapter: View Detached item = Item 1 [pos=1,type=0]
... I/Adapter: View Detached item = Item 0 [pos=0,type=0]
... I/Adapter: View Recycled item = Item C [pos=12,type=0]
... I/Adapter: Bind ViewHolder!! position = 12
アイテムCのプリフェッチが無くなってしまいました。ここで、再びアイテムCをプリフェッチする動作が発生します。
アイテムCが表示されるまでに、プリフェッチが2回行われる結果になります。この動作は無駄です。
無駄を無くすには、キャッシュサイズを6にして”プリフェッチ”と”非表示による保持”が共存できるようにします。
ですが …
最近のAndroid端末はとても高速なので、この程度の改善くらいでは体感できないことがほとんどです。
関連記事: