RecyclerView:キャッシュ(RecyclerPool)のサイズ

投稿日:  更新日:

RecyclerViewのリサイクル動作で使われるキャッシュは、サイズを大きくすれば多くのViewHolderが保持できます。その分、多くのメモリを消費します。

ViewHolderを多く保持できたとしても、サイクル動作で効率よく使われなければ、メモリの浪費です。

キャッシュのサイズはRecyclerViewの使われ方よって適切なサイズがあります。

そのため、RecyclerViewはキャッシュのサイズを変更できるようになっています。

スポンサーリンク

RecyclerPoolの構成とデフォルトサイズ

RecyclerPoolはRecyclerView.RecyclerViewPoolクラスで管理されています。

図に示したような構成になっていて、mScrapを参照する時のKeyはViewTypeです。

※SparseArray<T>はInt型をKeyにしたHashMapです。T型のオブジェクトを格納します。

RecyclerPoolの構成

public class RecyclerView extends ViewGroup implements ScrollingView,
        NestedScrollingChild2, NestedScrollingChild3 {
		
    ... // 5635行近辺
    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;
        }
        SparseArray<ScrapData> mScrap = new SparseArray<>();
		
		...
    }
	
	... // 5896行近辺
    public final class Recycler {
        ...
        RecycledViewPool mRecyclerPool;
		
		... // 6868行近辺
        RecycledViewPool getRecycledViewPool() {
            if (mRecyclerPool == null) {
                mRecyclerPool = new RecycledViewPool();
            }
            return mRecyclerPool;
        }
		...
	}
	...
}

RecyclerPoolのサイズはScrapData#mMaxScrapフィールドが持っています。

ScrapDataデータクラスがインスタンス化された時、DEFAULT_MAX_SCRAPが代入されるのでデフォルトサイズは5になります。

スポンサーリンク

RecyclerPoolのサイズ変更

サイズはRecyclerViewPool#setMaxRecycledViews( )メソッドを使用して変更できます。

ViewType毎に指定が可能です。

public class RecyclerView extends ViewGroup implements ScrollingView,
        NestedScrollingChild2, NestedScrollingChild3 {
	
	... // 367行近辺
    final Recycler mRecycler = new Recycler();

	... // 1486行近辺
    public RecycledViewPool getRecycledViewPool() {
        return mRecycler.getRecycledViewPool();
    }
	
    ... // 5635行近辺
    public static class RecycledViewPool {

        ... // 5678行近辺
        public void setMaxRecycledViews(int viewType, int max) {
            ScrapData scrapData = getScrapDataForType(viewType);
            scrapData.mMaxScrap = max;
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
            while (scrapHeap.size() > max) {
                scrapHeap.remove(scrapHeap.size() - 1);
            }
        }
		...
    }
	
    ... // 5896行近辺
    public final class Recycler {
        ...
        RecycledViewPool mRecyclerPool;
         
        ... // 6868行近辺
        RecycledViewPool getRecycledViewPool() {
            if (mRecyclerPool == null) {
                mRecyclerPool = new RecycledViewPool();
            }
            return mRecyclerPool;
        }
        ...
    }
    ...
}

例えば、RecyclerPoolのサイズを7に変更したければ次のように記述します。

val _viewType = 0	// デフォルトのViewType
rcySample.recycledViewPool.setMaxRecycledViews(_viewType, 7)
スポンサーリンク

RecyclerPoolの適切なサイズ

キャッシュのサイズを変更できることがわかったところで、最適なサイズを考察してみようと思います。

例えば、アイテムが7つ表示されているRecyclerViewで全データを更新してみます。

全データの更新ですから、データの参照先を変更して、変更の通知はnotifyDataSetChangedで行います。

        ...
        val _items = mutableListOf(
                "Item 0", "Item 1", "Item 2", "Item 3",
                "Item 4", "Item 5", "Item 6", "Item 7",
                "Item 8", "Item 9", "Item A", "Item B",
                "Item C", "Item D", "Item E", "Item F"
        )
		change(_items)
		...
    }

    private fun change(items: MutableList<String>) {
        val _adapter = rcySample.adapter as ListAdapter
        _adapter.items = items                                  // データの変更
        _adapter.notifyDataSetChanged()                         // 変更の通知(表示を更新)
    }
000102
NotifyDataSetChanged時のキャッシュ

... I/Adapter:     View Detached item = ITEM 6 [pos=-1,type=0]
... I/Adapter: View Recycled item = ITEM 6 [pos=-1,type=0]
... I/Adapter:     View Detached item = ITEM 5 [pos=-1,type=0]
... I/Adapter: View Recycled item = ITEM 5 [pos=-1,type=0]
※ポジション6~0まで同じ
... I/Adapter:   Bind   ViewHolder!! position = 0
... I/Adapter:     View Attached item = Item 0 [pos=0,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 1
... I/Adapter:     View Attached item = Item 1 [pos=1,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 2
... I/Adapter:     View Attached item = Item 2 [pos=2,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 3
... I/Adapter:     View Attached item = Item 3 [pos=3,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 4
... I/Adapter:     View Attached item = Item 4 [pos=4,type=0]
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter:   Bind   ViewHolder!! position = 5
... I/Adapter:     View Attached item = Item 5 [pos=5,type=0]
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter:   Bind   ViewHolder!! position = 6
... I/Adapter:     View Attached item = Item 6 [pos=6,type=0]
NotifyDataSetChanged時のキャッシュ

... I/Adapter:     View Detached item = ITEM 6 [pos=-1,type=0]
... I/Adapter: View Recycled item = ITEM 6 [pos=-1,type=0]
... I/Adapter:     View Detached item = ITEM 5 [pos=-1,type=0]
... I/Adapter: View Recycled item = ITEM 5 [pos=-1,type=0]
※ポジション6~0まで同じ
... I/Adapter:   Bind   ViewHolder!! position = 0
... I/Adapter:     View Attached item = Item 0 [pos=0,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 1
... I/Adapter:     View Attached item = Item 1 [pos=1,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 2
... I/Adapter:     View Attached item = Item 2 [pos=2,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 3
... I/Adapter:     View Attached item = Item 3 [pos=3,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 4
... I/Adapter:     View Attached item = Item 4 [pos=4,type=0]
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter:   Bind   ViewHolder!! position = 5
... I/Adapter:     View Attached item = Item 5 [pos=5,type=0]
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter:   Bind   ViewHolder!! position = 6
... I/Adapter:     View Attached item = Item 6 [pos=6,type=0]

notifyDataSetChengedは更新なので、旧アイテムが新アイテムに置き換わります。新旧のアイテムはエレメントの情報が異なります。よって、DetachedされたViewHolderは同一のポジションへ再生利用できません。

直接RecyclerPoolへキャッシュされますが、デフォルトサイズが5のため、7つ中の5つはキャッシュされ、溢れてしまった2つは破棄されてしまいます。

NotifyDataSetChanged時のキャッシュ

... I/Adapter:     View Detached item = ITEM 6 [pos=-1,type=0]
... I/Adapter: View Recycled item = ITEM 6 [pos=-1,type=0]
... I/Adapter:     View Detached item = ITEM 5 [pos=-1,type=0]
... I/Adapter: View Recycled item = ITEM 5 [pos=-1,type=0]
※ポジション6~0まで同じ
... I/Adapter:   Bind   ViewHolder!! position = 0
... I/Adapter:     View Attached item = Item 0 [pos=0,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 1
... I/Adapter:     View Attached item = Item 1 [pos=1,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 2
... I/Adapter:     View Attached item = Item 2 [pos=2,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 3
... I/Adapter:     View Attached item = Item 3 [pos=3,type=0]
... I/Adapter:   Bind   ViewHolder!! position = 4
... I/Adapter:     View Attached item = Item 4 [pos=4,type=0]
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter:   Bind   ViewHolder!! position = 5
... I/Adapter:     View Attached item = Item 5 [pos=5,type=0]
... I/Adapter: Create ViewHolder!! viewType = 0
... I/Adapter:   Bind   ViewHolder!! position = 6
... I/Adapter:     View Attached item = Item 6 [pos=6,type=0]

アイテムの再表示では、RecyclerPoolから5つが再生利用されます。残りの2つはキャッシュ上に無いので新規作成になります。

キャッシュサイズ5に対して表示するアイテムが7つのため、ViewHolderの新規作成が必要になり、findViewById(重い処理)を含んだonCreateVireHolderを実行しなければなりません。

onCreateVireHolderの実行を無くすには、キャッシュサイズ7に変更して、更新前のアイテムを全てキャッシュ可能にします。

ですが …

最近のAndroid端末はとても高速なので、この程度の改善くらいでは体感できないことがほとんどです。

スポンサーリンク

関連記事:

RecyclerViewは子Viewを並べて表示するコンテナタイプ(ConstraintLayoutと同じ)のViewです。 複数のデータをスクリーン上に一覧表示したい時、例えば電話帳のような「氏名+住所+電話番号」の一覧を表示する場合などに最適です。 アプリを開発していると一覧表示したいデータが多いことに気付きます。 なのでRecyclerViewはとても重要で重宝するViewです。 しかし、思い通りの表示を行わせるためのテクニックが多すぎて、使いこなしが難しいです。 今まで調べたテクニックを忘れないように、整理して書き残そうと思います。 今回は基本の「RecyclerViewの実装」です。 ...
RecyclerViewでアイテムのクリックイベントを取得し、処理を実行する方法を紹介します。 ...
RecyclerViewはアイテムのレイアウトをアイテム毎に変更できます。その時に使う値がViewTypeです。 ViewTypeでアイテムのレイアウトを変更する方法を紹介します。 ...
RecyclerViewは表示が変更される(アイテムの更新、スクロール)時、アイテムのViewをリサイクル(再生利用)します。 これにより余分なViewの作成が行われなくなり、メモリーの節約とパフォーマンスの向上が望めます。 リサイクルはCachedViewsとRecyclerPoolという2つのキャッシュで行われます。 このキャッシュを使ったリサイクルの動作を調べたので紹介します。 ...
RecyclerViewのリサイクル動作で使われるキャッシュは、サイズを大きくすれば多くのViewHolderが保持できます。その分、多くのメモリを消費します。 ViewHolderを多く保持できたとしても、サイクル動作で効率よく使われなければ、メモリの浪費です。 キャッシュのサイズはRecyclerViewの使われ方よって適切なサイズがあります。 そのため、RecyclerViewはキャッシュのサイズを変更できるようになっています。 ...
RecyclerViewはアイテムを一覧表示してくれます。 ただ一覧表示するだけではなく、「追加・削除・移動・切り替え」といったアイテムの表示を効率よく変更する仕組み持っています。 今回はこの仕組みを使ったアイテムの変更方法を紹介します。 ...
RecyclerViewが空(アイテムが無い)の時、EmptyViewを表示する実装を行ったので紹介します。 ...
RecyclerView上のアイテムを選択する方法を紹介します。 外部ライブラリー(AndroidX)で提供されるrecyclerview-selection APIを用いた方法です。 ...
RecyclerViewはListView(RecyclerViewの前身)の時に存在していたChoiceModeがありません。 同様な機能が欲しければプログラマ側で実装しなければなりません。 RecyclerView.AdapterをカスタマイズしてChoiceModeを実装してみたので紹介します。 ...
RecyclerViewはアイテムへ装飾を付けることが出来るようになっています。 装飾とは、例えばアイテムの区切り線などです。 今回はアイテムへ装飾を付ける方法を紹介します。 ...
RecyclerViewに表示しきれなかったアイテムはスクロールを行うことで表示されるようになっています。 スクロールは「外部入力(指でスクリーン上をタッチしてスライド)によるスクロール」の他に、「プログラムによるスクロール」をすることも出来ます。 今回はこのアイテムのスクロールについてまとめてみました。 ...
RecyclerViewはアイテムをスクロールさせて隠れたアイテムを表示できます。 スクロールを止める位置は任意です。 任意であるがゆえに、止めた位置によってはアイテムの一部が欠けてしまうこともあります。 携帯端末の画面は狭いので、効率よくコンテンツの表示を行いたいとアプリ開発者は考えます。 アイテムの一部が欠けてしまうことは、効率が良いとは言えません。 このような問題を解決するために、アイテムのスナップをRecyclerViewへ追加できます。 アイテムのスナップを追加する方法を紹介します。 ...
RecyclerViewでアイテムの変更(Change/Insert/Move/Remove)を行うと、変更される様子がアニメーション化されています。 これはデフォルトでアイテムの変更アニメーションが組み込まれているためです。 デフォルトは単純なアニメーションですが、「ある」と「ない」の違いは歴然で、アニメーションのある方が高価なアプリケーションに見えます。 GUI(Graphical User Interface)が主体の携帯端末にとって、利用者に対するアプリの見せ方は重要です。高価に見えた方が使ってもらえる可能性が高くなります。 上記のことから、アプリの機能に関係なくても、ちょっとした動きをアニメーション化するメリットがあります。 2回にわたりアイテムの変更アニメーションについてまとめてみました。 アイテムの変更アニメーション(DefaultItemAnimator、デフォルト) アイテムの変更アニメーション(SimpleItemAnimatorの継承、カスタム) 今回は第1回目「ItemAnimator、デフォルト」編です。デフォルト変更アニメーションの動作について説明します。 ...
RecyclerViewでアイテムの変更(Change/Insert/Move/Remove)を行うと、変更される様子がアニメーション化されています。 これはデフォルトでアイテムの変更アニメーションが組み込まれているためです。 デフォルトは単純なアニメーションですが、「ある」と「ない」の違いは歴然で、アニメーションのある方が高価なアプリケーションに見えます。 GUI(Graphical User Interface)が主体の携帯端末にとって、利用者に対するアプリの見せ方は重要です。高価に見えた方が使ってもらえる可能性が高くなります。 上記のことから、アプリの機能に関係なくても、ちょっとした動きをアニメーション化するメリットがあります。 2回にわたりアイテムの変更アニメーションについてまとめてみました。 アイテムの変更アニメーション(DefaultItemAnimator、デフォルト) アイテムの変更アニメーション(SimpleItemAnimatorの継承、カスタム) 今回は第2回目「SimpleItemAnimatorの継承、カスタム」編です。カスタム変更アニメーションの作り方を説明 ...
RecyclerViewへアイテムが表示されるとき、アニメーションはありません。。一瞬で表示されて終わりです。 「RecyclerViewへアイテムが表示される」ことを、ここでは「アイテムの出現」と言い表すことにします。 このアイテムの出現にアニメーションを付ける方法を紹介します。 アイテムの出現をアニメーションで演出することで、RecyclerViewに表示したい内容が際立つと思います。 ...
スポンサーリンク