Help us understand the problem. What is going on with this article?

Epoxyのスクロールがかくつくので調べたらリサイクルされていなかった

この記事はand factory Advent Calendar 2019の24日目の記事です

きっかけ

とあるページの初回表示を高速化するためにをEpoxyのRecyclerView(以下、Epoxyと書きます)を使うようリファクタしました。

対象となるページの構成はこちらです。rootがLinearlayoutでその下にいくつかのViewをぶら下がっています。

LinearLayout // rootのViewGroup
  ∟ RecyclerView
  ∟ RelativeLayout
  ∟ RecyclerView
  ∟ RecyclerView
  ∟ LinearLayout
  ∟ ...
  • 要素毎にaddViewしています。ただ、要素数がそれなりに多く、ページ初回表示するときに画面外の要素の処理も動いていました。

  • Epoxyでは画面外要素の処理はページ表示した時に動かないので、ページ表示の高速化が期待できそうです :relaxed:

LinearLayoutをEpoxyに切り替える

  • 先ほどのrootに配置していたLinearLayoutをEpoxyに切り替えて、子ビューのコードをEpoxyModelWithHolderを拡張したクラスに移動しました

動作テスト

  • 起動時のページ表示が早くなった(体感)気がします。少なくとも画面外の要素の処理は走っていないです。

  • 古い端末だとスクロールでちょいちょいかくつきが気になります。

  • 上下のスクロールを何度繰り返してもカクツクので、キャッシュが効いていなさそうです。EpoxyのViewPool周りの実装をみていきました。

EppxyがViewPoolを管理している箇所

ActivityRecyclerPool.kt
internal class PoolReference(
    context: Context,
    val viewPool: RecyclerView.RecycledViewPool,
    private val parent: ActivityRecyclerPool
) : LifecycleObserver {
    private val contextReference: WeakReference<Context> = WeakReference(context)

    val context: Context? get() = contextReference.get()
  • Epoxyは独自でViewPoolを管理しているようです。RecyclerViewでは1つのActivityで保持するインスタンスがViewTypeごとに5つまでと制限されていますが、Epoxyはこの制限が外されています。この辺りの仕様からもEpoxyとRecyclerViewが別々の実装を持っていることがなんとなくわかります。

  • ActivityRecyclerPool.kt

RecyclerView内でViewPoolからキャッシュを取得するコード

RecyclerView.java
// この辺りでキャッシュを探しているようです(おそらく)

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
            final int scrapCount = mAttachedScrap.size();
// 省略..

            final int cacheSize = mCachedViews.size();
            for (int i = 0; i < cacheSize; i++) {
                final ViewHolder holder = mCachedViews.get(i);


  • RecyclerViewでも保持しているViewPoolからキャッシュを探しているようです
  • メソッド名からにじみ出ていますが、ViewPool以外でもキャッシュを保持しているクラスがいるようですね。adapterとかmChildHelperとか、どんな役割なのか気になります。
  • RecyclerView.java

原因

  • 今回のリファクタでrootのLinearLayoutをEpoxyに変更しましたが。子ビューで使っていたRecyclerViewはそのままだったのが原因でした。

  • Epoxyは1つのActivity内のEpoxy同士で描画したViewをViewPoolにキャッシュして次回からリサイクルできますが、RecyclerviewのViewPoolはEpoxyのものとは別管理のため毎回inflateが発生していたため、古い端末でかくつきが起こっていました。:zap:

対応

  • EpoxyとRecyclerViewの混在するとViewPoolの共有が上手くいかないことがわかったので、RecyclerViewをEpoxyに変更して古い端末でもカクツキが軽減されたことを確認しました。

まとめ

  • EpoxyはネストされたEpoxyでもViewPoolをデフォルトで共有してくれるので便利です。
  • Epoxy使っているのにカクツクことがある場合は、子ビューにRecyclerViewが紛れていないか確認すると良いかもしれません。
  • 公式でViePoolのことがちゃんと書いてあるので、しっかり読むの大事だなと思いました

参考

yst_i
Androidエンジニアです。経験が自分以外に役立つなら嬉しいです。qiitaでは経験を整理した記事が主です。言語はJava、JavaScript、PHP、Swift。アジャイルな開発を目指してテスト・CIに取り組んでます。最近離れてますけど、サーバーサイドエンジニアでした。
andfactory
Smartphone Idea Companyとして、人々の生活に「&(アンド)」を届ける。
https://andfactory.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした