RecycledViewPoolを共有する場合はPoolが保持可能なViewの個数に気をつけよう

# TL;DR

- [RecycledViewPool](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.RecycledViewPool.html) は、デフォルトでは、View Typeごとに最大5つまでしかViewを保持しない設定になっています
- 複数の `RecyclerView` で単一の `RecycledViewPool` を共有している場合は、`RecycledViewPool` が保持するViewの最大数を [RecycledViewPool.setMaxRecycledViews()](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.RecycledViewPool.html#setMaxRecycledViews(int,%20int)) で増やしておくと、`RecycledViewPool`共有の効果がさらに高まるかもしれません

```kotlin:setMaxRecycledViews()でプールサイズを増やす例
val viewPool = RecyclerView.RecycledViewPool()
viewPool.setMaxRecycledViews(VIEW_TYPE_HEADER, 128)
viewPool.setMaxRecycledViews(VIEW_TYPE_ITEM, 128)
```

# きっかけ

「[RecyclerViewのViewPoolを共有してInflate回数を劇的に減らす](https://qiita.com/chibatching/items/19ec43c62db2e38ce673)」を読んで[RecycledViewPool](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.RecycledViewPool.html)を複数の[RecyclerView](https://developer.android.com/reference/androidx/recyclerview/widget/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は保持されずに破棄されます](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.RecycledViewPool.html#putRecycledView(androidx.recyclerview.widget.RecyclerView.ViewHolder))。

> If the pool is already full for that ViewHolder's type, it will be immediately discarded.

この最大保持数は、デフォルトでは `5` に設定されています (参考: [RecyclerView.java#5519](https://android.googlesource.com/platform/frameworks/support/+/237c8946756af4b0fe9d0fa3965593e247d53698/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java#5519))

```java:RecyclerView.java
    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()](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.RecycledViewPool.html#setMaxRecycledViews(int,%20int)) で変更できるようになっています。大きい値を設定したら即座にView Poolが伸長される、ということもないので、大きめの値を設定しても特に問題ないです (参考: [RecyclerView.java#5561](https://android.googlesource.com/platform/frameworks/support/+/237c8946756af4b0fe9d0fa3965593e247d53698/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java#5561))。

弊社アプリでは、試しに `128` を設定したところ、ある程度の数のViewがInflationされた後は、`ViewPager` のページを移動したり `RecyclerView` をスクロールしたりしてもViewがInflationされなくなり、期待通りの動作となりました。

# おまけ: そもそもRecyclerViewが破棄されたらViewはリサイクルされるのか

`LinearLayoutManager` やその派生クラスの `GridLayoutManager` を使っている場合は、[LinearLayoutManager.setRecycleChildrenOnDetach()](https://developer.android.com/reference/androidx/recyclerview/widget/LinearLayoutManager.html#setRecycleChildrenOnDetach(boolean))で `true` を設定しておけば、`RecyclerView` がWindowからDetachされるときにViewがリサイクルされます[^1]。

仕組みとしては、`RecyclerView` がWindowからDetachされたときに、`LaoutManager` の [onDetachedFromWindow()](https://developer.android.com/reference/androidx/recyclerview/widget/LinearLayoutManager.html#onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView,%20androidx.recyclerview.widget.RecyclerView.Recycler)) が呼ばれるのですが、`LinearLayoutManager` は、[setRecycleChildrenOnDetach()](https://developer.android.com/reference/androidx/recyclerview/widget/LinearLayoutManager.html#setRecycleChildrenOnDetach(boolean)) で `true` が設定されていたら、`onDetachedFromWindow()` において [removeAndRecycleAllViews()](https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.LayoutManager.html#removeAndRecycleAllViews(androidx.recyclerview.widget.RecyclerView.Recycler)) を呼び出して全てのViewをリサイクルする、ということのようです (参考: [LinearLayoutManager.java#231](https://android.googlesource.com/platform/frameworks/support/+/237c8946756af4b0fe9d0fa3965593e247d53698/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java#231))。

```java:LinearLayoutManager.java
    @Override
    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
        super.onDetachedFromWindow(view, recycler);
        if (mRecycleChildrenOnDetach) {
            removeAndRecycleAllViews(recycler);
            recycler.clear();
        }
    }
```

[^1]: 「[RecyclerViewのViewPoolを共有してInflate回数を劇的に減らす](https://qiita.com/chibatching/items/19ec43c62db2e38ce673)」でも紹介されています