TL;DR
- RecycledViewPool は、デフォルトでは、View Typeごとに最大5つまでしかViewを保持しない設定になっています
- 複数の
RecyclerView
で単一のRecycledViewPool
を共有している場合は、RecycledViewPool
が保持するViewの最大数を RecycledViewPool.setMaxRecycledViews() で増やしておくと、RecycledViewPool
共有の効果がさらに高まるかもしれません
val viewPool = RecyclerView.RecycledViewPool()
viewPool.setMaxRecycledViews(VIEW_TYPE_HEADER, 128)
viewPool.setMaxRecycledViews(VIEW_TYPE_ITEM, 128)
きっかけ
「RecyclerViewのViewPoolを共有してInflate回数を劇的に減らす」を読んでRecycledViewPoolを複数のRecyclerViewで共有できることを知り、RecyclerView
を ViewPager
の中で使っている画面で試してみました。
このテクニックを適用した場合、RecyclerView
をスクロールしたり ViewPager
のページを移動したりして、ある程度の数のViewをInflationすれば、あとは新しいViewの表示は全て RecycledViewPool
からのリサイクルでまかなえるようになり、ViewのInflationはなくなると思っていました。ただ、実際にやってみると、いつまでたっても、ページを移動するたびに何個かViewがInflationされる挙動が止まりませんでした。
変だな、と思って調べたところ、RecycledViewPool
が保持するViewの最大数がデフォルトではView Typeごとに5個に設定されており、Poolサイズが足りていないことが原因だとわかった、という次第です。
RecycledViewPoolが保持するViewの数
RecycledViewPool
は、View Typeごとに保持するViewの数に制限を設けており、その数を超えてViewをリサイクルしようとすると、当該Viewは保持されずに破棄されます。
If the pool is already full for that ViewHolder's type, it will be immediately discarded.
この最大保持数は、デフォルトでは 5
に設定されています (参考: RecyclerView.java#5519)
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
RecycledViewPool
を共有しない場合は、Viewがリサイクルされるのはスクロールにより画面外に出た場合なので、View Typeごとに5個保持されれば十分そうです。一方、RecycledViewPool
を共有する場合は、RecyclerView
が使われなくなったらその RecyclerView
が表示していたViewを全部リサイクルしたいので、5個では足りないケースが多そうです。
View Typeごとの保持最大数は RecycledViewPool.setMaxRecycledViews() で変更できるようになっています。大きい値を設定したら即座にView Poolが伸長される、ということもないので、大きめの値を設定しても特に問題ないです (参考: RecyclerView.java#5561)。
弊社アプリでは、試しに 128
を設定したところ、ある程度の数のViewがInflationされた後は、ViewPager
のページを移動したり RecyclerView
をスクロールしたりしてもViewがInflationされなくなり、期待通りの動作となりました。
おまけ: そもそもRecyclerViewが破棄されたらViewはリサイクルされるのか
LinearLayoutManager
やその派生クラスの GridLayoutManager
を使っている場合は、LinearLayoutManager.setRecycleChildrenOnDetach()で true
を設定しておけば、RecyclerView
がWindowからDetachされるときにViewがリサイクルされます1。
仕組みとしては、RecyclerView
がWindowからDetachされたときに、LaoutManager
の onDetachedFromWindow() が呼ばれるのですが、LinearLayoutManager
は、setRecycleChildrenOnDetach() で true
が設定されていたら、onDetachedFromWindow()
において removeAndRecycleAllViews() を呼び出して全てのViewをリサイクルする、ということのようです (参考: LinearLayoutManager.java#231)。
@Override
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
super.onDetachedFromWindow(view, recycler);
if (mRecycleChildrenOnDetach) {
removeAndRecycleAllViews(recycler);
recycler.clear();
}
}
-
「RecyclerViewのViewPoolを共有してInflate回数を劇的に減らす」でも紹介されています ↩