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端末はとても高速なので、この程度の改善くらいでは体感できないことがほとんどです。
関連記事:
