RecyclerViewのパフォーマンスチューニング

パフォーマンスチューニングと書くと大げさかもしれない。 最近何度か RecyclerViewのパフォーマンス面の修正をする機会があったので、最低限これくらい見ておくと良さそう、という項目についてまとめました。

各アイテムのstable ID

Adapterの各アイテムが固有のIDを持つ場合、stable IDを利用するとnotifyDataSetChanged() 呼び出し時の各アイテム再描画など様々な場面で有利になります。

利用するためには、AdapterのgetItemId(int position)を実装した上でsetHasStableIds(true)を呼び出します。

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#setHasStableIds(boolean)

notify〜系を正しく呼び出す

Adapterの内容を更新する際、Adapter#notifyDataSetChanged()を呼んでしまいがちですがnotifyItemInserted()notifyItemRemoved()を適切に呼び出すことで不要なViewの再描画を防ぐことができます。 この修正もstable IDを実装しておくとより効果が高くなりそうです。

なお、Support Library 24.2.0 からは DiffUtilが追加され、notify〜を個別に呼ばなくてもリストの比較処理を行うメソッドをいくつか実装するだけでadapterの更新ができるようになりました。

https://developer.android.com/reference/android/support/v7/util/DiffUtil.html

アイテムのキャッシュ

LinearLayoutManagerの場合、getExtraLayoutSpace()をOverrideし、大きな値を返すことによってアイテムをプリキャッシュさせることができます。 これにより、プリキャッシュのためのコストと引き換えにスクロール時のパフォーマンスを改善させることができます。 どのような値が適しているかは状況次第ですが、LinearLayoutManagerのorientationにあわせて画面の縦横サイズをそのまま返す実装が一般的なようです(多分)。

https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html#getExtraLayoutSpace(android.support.v7.widget.RecyclerView.State)

また、RecyclerView自体もsetItemViewCacheSize()というメソッドによりViewキャッシュの個数を増やすことができ、こちらもスクロール時のパフォーマンスに影響します。

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setItemViewCacheSize(int)

onViewRecycled を活用する

onBindViewHolder()で時間のかかる処理を行っている場合、onViewRecycled()でキャンセルする処理を入れましょう。

RecyclerViewサイズの固定化

Adapterの内容がRecyclerViewのサイズに影響しない場合、setHasFixedSize (true)を設定することでパフォーマンスが向上します。

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setHasFixedSize(boolean)

ネストされたRecyclerViewのプリフェッチ

縦方向のRecyclerViewの中に横方向のRecyclerViewをネストする、いわゆるカルーセル表示のUIの場合、setInitialPrefetchItemCountで初期表示されるアイテムの個数を設定しておくことで親RecyclerViewのスクロール時に有利になるようです。 (Support Library 25.1.0で追加されましたが、まだ十分検証していません)

https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html#setInitialPrefetchItemCount(int)

Adapter差し替え処理の最適化

何らかの理由でRecyclerViewに同一クラス/別インスタスなAdapterを設定する場合、setAdapter()の代わりにswapAdapter()を利用することができます。 swapAdapter()はRecycledViewPoolをクリアしないためViewが再利用されパフォーマンス面で有利となります。 先述の stable ID を設定している場合はより有用です。

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#swapAdapter(android.support.v7.widget.RecyclerView.Adapter,%20boolean)

LayoutParamsを動的に生成しない

RecyclerViewnに限った話ではないですが、LayoutParamsを動的に生成してsetするのは避けたほうが良いです。 稀にStaggeredGridLayoutManager.LayoutParamssetFullSpan()を呼び出すためだけに動的に生成しているコードを見かけますが、通常は アイテムのroot viewからgetLayoutParams()するとStaggeredGridLayoutManager.LayoutParamsになっているので、これを再利用してください。

これはパフォーマンスへの影響ももちろんですが、レイアウトxmlに記述したlayout_widthやlayout_height、marginの設定消失を避ける意味でも重要です。