AndroidRecyclerViewのスクロールパフォーマンス


88

リストとカードの作成ガイドに基づいて、RecyclerViewの例を作成しました。私のアダプターには、レイアウトを膨らませるためだけのパターン実装があります。

問題は、スクロールパフォーマンスが低いことです。これは、8つのアイテムしかないRecycleViewにあります。

一部のテストでは、AndroidLではこの問題が発生しないことを確認しました。しかし、KitKatバージョンでは、パフォーマンスの低下が明らかです。


1
パフォーマンスをスクロールするViewHolderデザインパターンを使用してみてください:developer.android.com/training/improving-layouts/...
Haresh Chhelana

@HareshChhelana回答ありがとうございます!しかし、リンクによると、私はすでにViewHolderパターンを使用しています:developer.android.com/training/material/lists-cards.html
falvojr 2014年

2
アダプターのセットアップとレイアウトのXMLファイルに関するコードを共有できますか。これは正常に見えません。また、プロファイルを作成して、時間がどこで費やされているかを確認しましたか?
yigit 2014年

2
私はほとんど同じ問題に直面しています。前ロリポップでの速い除きと信じられない(本当に)AndroidのL.に鈍化
Servus7

1
インポートしているライブラリのバージョンを共有することもできますか。
ドロイドカ2016

回答:


231

最近同じ問題に直面したので、これが最新のRecyclerViewサポートライブラリで行ったことです。

  1. 複雑なレイアウト(ネストされたビュー、RelativeLayout)を新しい最適化されたConstraintLayoutに置き換えます。AndroidStudioでアクティブ化します。[SDKマネージャー]-> [SDKツール]タブ-> [サポートリポジトリ]-> [AndroidのConstraintLayout]と[Solver]の[ConstraintLayout]を確認します。依存関係に追加します。

    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    
  2. 可能であれば、RecyclerViewのすべての要素を同じ高さにします。そして追加:

    recyclerView.setHasFixedSize(true);
    
  3. デフォルトのRecyclerView描画キャッシュメソッドを使用し、ケースに応じてそれらを微調整します。そのためにサードパーティのライブラリは必要ありません。

    recyclerView.setItemViewCacheSize(20);
    recyclerView.setDrawingCacheEnabled(true);
    recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
    
  4. 多くの画像を使用する場合は、それらのサイズと圧縮が最適であることを確認してください。画像のスケーリングもパフォーマンスに影響を与える可能性があります。問題には、使用されるソースイメージとデコードされたビットマップの2つの側面があります。次の例は、Webからダウンロードした画像をデコードする方法のヒントを示しています。

    InputStream is = (InputStream) url.getContent();
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    Bitmap image = BitmapFactory.decodeStream(is, null, options);
    

最も重要な部分は指定ですinPreferredConfig-それは画像の各ピクセルに使用されるバイト数を定義します。これは推奨されるオプションであることに注意してください。ソース画像の色が多い場合でも、別の構成でデコードされます。

  1. onBindViewHolder()が可能な限り安価であることを確認してください。OnClickListenerを一度設定onCreateViewHolder()し、インターフェイスを介してアダプタの外部のリスナーを呼び出し、クリックされたアイテムを渡すことができます。このようにして、常に余分なオブジェクトを作成する必要はありません。ここでビューに変更を加える前に、フラグと状態も確認してください。

    viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              Item item = getItem(getAdapterPosition());
              outsideClickListener.onItemClicked(item);
          }
    });
    
  2. データが変更された場合は、影響を受けるアイテムのみ更新してみてください。たとえば、データセット全体をで無効にする代わりに、notifyDataSetChanged()アイテムを追加/ロードするときは、次を使用します。

    adapter.notifyItemRangeInserted(rangeStart, rangeEnd);
    adapter.notifyItemRemoved(position);
    adapter.notifyItemChanged(position);
    adapter.notifyItemInserted(position);
    
  3. Androidデベロッパーウェブサイト

最後の手段としてnotifyDataSetChanged()に依存します。

ただし、使用する必要がある場合は、アイテムを一意のIDで維持してください。

    adapter.setHasStableIds(true);

RecyclerViewは、このメソッドが使用されると、安定したIDを持っていることを報告するアダプターの目に見える構造変更イベントを合成しようとします。これは、アニメーションとビジュアルオブジェクトの永続性の目的に役立ちますが、個々のアイテムビューは、リバウンドして再レイアウトする必要があります。

すべてを正しく行ったとしても、RecyclerViewが期待どおりにスムーズに実行されていない可能性があります。


20
adapter.setHasStableIds(true);に1票 recyclerviewを高速化するのに本当に役立った方法。
Atula 2016年

1
パート7は完全に間違っています!setHasStableIds(true)は、adapter.notifyDataSetChanged()を使用する以外は何もしません。リンク:developer.android.com/reference/android/support/v7/widget/...
localhostの

1
recyclerView.setItemViewCacheSize(20);がパフォーマンスを損なう理由がわかります。しかしrecyclerView.setDrawingCacheEnabled(true);recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);しかし!これらが何かを変えるかどうかはわかりません。これらはView、図面キャッシュをビットマップとしてプログラムで取得し、後でそれを利用できるようにする特定の呼び出しです。RecyclerViewそれについては何もしていないようです。
Abdelhakim AKODADI 2017年

2
@AbdelhakimAkodadi、スクロールはキャッシュでスムーズになります。私はそれをテストしました。そうでなければ、それは明らかです。もちろん、誰かが狂ったようにスクロールした場合、何も役に立ちません。私の場合は画質が重要なので、使用しないsetDrawingCacheQualityのような他のオプションを表示するだけです。DRAWING_CACHE_QUALI‌ TY_HIGHについては説教しませんが、興味のある人は誰でも、オプションをさらに深く掘り下げて微調整することをお勧めします。
ガリア2017年

2
setDrawingCacheEnabled()およびsetDrawingCacheQuality()は非推奨になりました。代わりに、ハードウェアアクセラレーションを使用してください。developer.android.com/reference/android/view/...
Shayan_Aryan

14

次のフラグを追加することで、この問題を解決しました。

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


2
これにより、パフォーマンスがわずかに向上します。ただし、RecyclerViewは依然として非常に低速であり、同等のカスタムListViewよりもはるかに低速です。
SMBiggs 2016年

ああ、でも私のコードがとても遅い理由を発見しました-それはsetHasStableIds()とは何の関係もありません。詳細を記載した回答を投稿します。
SMBiggs 2016年

13

私はあなたのパフォーマンスを殺すことができる少なくとも1つのパターンを発見しました。それonBindViewHolder()頻繁に呼ばれることを忘れないでください。したがって、そのコードで行うことはすべて、パフォーマンスを停止させる可能性があります。RecyclerViewがカスタマイズを行う場合、このメソッドに誤ってスローコードを挿入するのは非常に簡単です。

位置に応じて、各RecyclerViewの背景画像を変更していました。ただし、画像の読み込みには少し手間がかかるため、RecyclerViewの動作が遅くなります。

画像のキャッシュを作成すると、驚異的に機能しました。onBindViewHolder()キャッシュされた画像への参照を最初からロードするのではなく、変更するだけです。これで、RecyclerViewが圧縮されます。

誰もがこの正確な問題を抱えているわけではないことを私は知っているので、私はコードをロードすることを気にしません。ただしonBindViewHolder()、RecyclerViewのパフォーマンスが低下する可能性のあるボトルネックとして、自分で行われる作業を考慮してください。


私は同じ問題を抱えています。現在、画像の読み込みとキャッシュにFrescoを使用しています。RecyclerView内で画像をロードおよびキャッシュするための別のより良いソリューションがありますか。ありがとう。
Androidicus 2016年

私はフレスコに慣れていません(読んで...彼らの約束は素晴らしいです)。たぶん、彼らはRecyclerViewsでキャッシュを最適に使用する方法についていくつかの洞察を持っています。そして、この問題を抱えているのはあなただけではないようです:github.com/facebook/fresco/issues/414
SMBiggs 2016年

10

@Galyaの詳細な回答に加えて、最適化の問題である可能性がありますが、デバッガーを有効にすると処理速度が大幅に低下する可能性があることも事実です。

を最適化するためにすべてを行っRecyclerViewてもスムーズに機能しない場合は、ビルドバリアントをに切り替えてrelease、非開発環境(デバッガーを無効にした状態)でどのように機能するかを確認してください。

debugビルドバリアントでアプリのパフォーマンスが遅いことがありましたが、releaseバリアントに切り替えるとすぐにスムーズに動作しました。これは、releaseビルドバリアントを使用して開発する必要があるという意味ではありませんが、アプリを出荷する準備ができているときはいつでも問題なく動作することを知っておくとよいでしょう。


このコメントは私にとって本当に役に立ちました!recyclerviewのパフォーマンスを向上させるためにあらゆることを試みましたが、実際には何も役に立ちませんでしたが、リリースビルドに切り替えると、すべてが問題ないことに気付きました。
タルバルダ2018

1
デバッガーが接続されていなくても同じ問題に直面しましたが、ビルドのリリースに移行するとすぐに問題は発生しなくなりました
Farmaan Elahi 2018

8

RecyclerViewのパフォーマンスについてお話を伺いました。こちらが英語のスライドロシア語の録画ビデオです。

一連のテクニックが含まれています(それらのいくつかはすでに@Daryaの回答でカバーされています)。

簡単な要約は次のとおりです。

  • Adapterアイテムのサイズが固定されている場合は、次のように設定します。
    recyclerView.setHasFixedSize(true);

  • データエンティティをlongで表すことができる場合(hashCode()たとえば)、次のように設定します。
    adapter.hasStableIds(true);
    実装:
    // YourAdapter.java
    @Override
    public long getItemId(int position) {
    return items.get(position).hashcode(); //id()
    }
    この場合Item.id()Itemコンテンツが変更されても同じままであるため、機能しません。
    PS DiffUtilを使用している場合、これは必要ありません。

  • 正しくスケーリングされたビットマップを使用します。車輪の再発明をしてライブラリを使用しないでください。
    選択方法の詳細はこちら

  • 常に最新バージョンのを使用してくださいRecyclerView。たとえば、25.1.0-プリフェッチでパフォーマンスが大幅に向上しました。
    詳細はこちら

  • DiffUtillを使用します。
    DiffUtilは必須です。
    公式ドキュメント

  • アイテムのレイアウトを簡素化してください!
    TextViewsを充実させるための小さなライブラリ-TextViewRichDrawable

詳細な説明については、スライドを参照してください。


7

setHasStableIdフラグを使用することで問題が解決するかどうかはよくわかりません。提供する情報に基づいて、パフォーマンスの問題はメモリの問題に関連している可能性があります。ユーザーインターフェイスとメモリに関するアプリケーションのパフォーマンスは、かなり関連しています。

先週、アプリがメモリリークしていることを発見しました。これを発見したのは、アプリを20分使用した後、UIのパフォーマンスが非常に遅いことに気付いたためです。アクティビティを閉じたり開いたり、要素の束を含むRecyclerViewをスクロールしたりするのは本当に遅かった。http://flowup.io/を使用して本番環境で一部のユーザーを監視した後、次のことがわかりました。

ここに画像の説明を入力してください

フレーム時間は本当に長く、1秒あたりのフレーム数は本当に低かった。:Sをレンダリングするのに約2秒かかるフレームがあることがわかります。

この悪いフレーム時間/ fpsの原因を突き止めようとすると、次のようにメモリの問題が発生したことがわかりました。

ここに画像の説明を入力してください

アプリがフレームをドロップしているのと同時に、平均メモリ消費量が15MBに近い場合でも。

それが私がUIの問題を発見した方法です。アプリでメモリリークが発生し、ガベージコレクターイベントが多数発生しました。これは、Android VMがアプリを停止してフレームごとにメモリを収集する必要があるため、UIのパフォーマンスが低下する原因でした。

コードを見ると、Android Choreographerインスタンスからリスナーの登録を解除していなかったため、カスタムビュー内にリークがありました。修正をリリースした後、すべてが正常になりました:)

メモリの問題が原因でアプリがフレームをドロップしている場合は、次の2つの一般的なエラーを確認する必要があります。

アプリが1秒間に複数回呼び出されるメソッド内にオブジェクトを割り当てているかどうかを確認します。この割り当ては、アプリケーションが遅くなっている別の場所で実行できる場合でも。例として、リサイクラービュービューホルダーのonBindViewHolderのonDrawカスタムビューメソッド内にオブジェクトの新しいインスタンスを作成する場合があります。アプリがインスタンスをAndroidSDKに登録しているが、リリースしていないかどうかを確認します。リスナーをバスイベントに登録すると、リークが発生する可能性もあります。

免責事項:アプリの監視に使用しているツールは開発中です。私は開発者の一人なので、このツールにアクセスできます:)このツールにアクセスしたい場合は、すぐにベータ版をリリースします!あなたは私たちのウェブサイトに参加することができます:http//flowup.io/

さまざまなツールを使用する場合は、traveview、dmtracedump、systrace、またはAndroidStudioに統合されたAndoridパフォーマンスモニターを使用できます。ただし、このツールは接続されたデバイスを監視し、残りのユーザーデバイスやAndroidOSのインストールは監視しないことに注意してください。


3

Recyclerviewに配置した親レイアウトを確認することも重要です。ネストされたscrollviewでrecyclerViewをテストしたときに、同様のスクロールの問題が発生しました。別のビューでスクロールするビューは、スクロール中にパフォーマンスが低下する可能性があります


2

私の場合、ラグの顕著な原因は#onBindViewHolder()メソッド内での頻繁なドローアブルロードであることがわかりました。ViewHolder内に一度画像をビットマップとしてロードするだけで解決しましたし、前述の方法からアクセス。それが私がしたすべてです。


2

RecyclerViewでは、item_layoutの背景にビットマップ画像を使用しています。
@Galyaが言ったことはすべて真実です(そして私は彼の素晴らしい答えに感謝します)。しかし、彼らは私のために働きませんでした。

これが私の問題を解決したものです:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

詳細については、この回答をお読みください 。


2

私の場合、複雑なrecyclerviewの子がいます。そのため、アクティビティの読み込み時間に影響しました(アクティビティのレンダリングでは約5秒)

postDelayed()でアダプタをロードします->これにより、アクティビティのレンダリングに良い結果が得られます。アクティビティの後、recyclerviewのロードをスムーズにレンダリングします。

この答えを試してください、

    recyclerView.postDelayed(new Runnable() {
        @Override
        public void run() {
            recyclerView.setAdapter(mAdapter);
        }
    },100); 

1

コメントですでにViewHolderパターンを実装していることがわかりますが、パターンを使用するアダプターの例をここに投稿RecyclerView.ViewHolderして、同様の方法で統合していることを確認できるようにします。ここでも、コンストラクターはニーズに応じて異なります。例です:

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {

    Context mContext;
    List<String> mNames;

    public RecyclerAdapter(Context context, List<String> names) {
        mContext = context;
        mNames = names;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext())
                .inflate(android.R.layout.simple_list_item_1, viewGroup, false);

        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        //Populate.
        if (mNames != null) {
            String name = mNames.get(position);

            viewHolder.name.setText(name);
        }
    }

    @Override
    public int getItemCount() {

        if (mNames != null)
            return mNames.size();
        else
            return 0;
    }

    /**
     * Static Class that holds the RecyclerView views. 
     */
    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView name;

        public ViewHolder(View itemView) {
            super(itemView);
            name = (TextView) itemView.findViewById(android.R.id.text1);
        }
    }
}

作業に問題がRecyclerView.ViewHolderある場合は、Gradleで常に確認できる適切な依存関係があることを確認してください

それがあなたの問題を解決することを願っています。


1

これにより、よりスムーズなスクロールが可能になりました。

アダプターのonFailedToRecycleView(ViewHolderホルダー)をオーバーライドします

進行中のアニメーション(ある場合)ホルダーを停止します。 "animateview" .clearAnimation();

trueを返すことを忘れないでください。


1

このコード行で解決しました

recyclerView.setNestedScrollingEnabled(false);

1

@Galyaの回答に加えて、bind viewHolderでは、Html.fromHtml()メソッドを使用していました。どうやらこれはパフォーマンスに影響を与えます。


0

ピカソライブラリで1行だけを使用してこの問題を解決します

。フィット()

Picasso.get().load(currentItem.getArtist_image())

                    .fit()//this wil auto get the size of image and reduce it 

                    .placeholder(R.drawable.ic_doctor)
                    .into(holder.img_uploaderProfile, new Callback() {
                        @Override
                        public void onSuccess() {


                        }

                        @Override
                        public void onError(Exception e) {
                            Toast.makeText(context, "Something Happend Wrong Uploader Image", Toast.LENGTH_LONG).show();
                        }
                    });
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.