web-dev-qa-db-ja.com

RecyclerViewのスクロール性能

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

問題はスクロールのパフォーマンスが悪いことです。これは、8項目のみのごみ箱での表示です。

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

61
falvojr

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

  1. 複雑なレイアウト(ネストされたビュー、RelativeLayout)を新しい最適化されたConstraintLayoutに置き換えます。 Android St​​udioで有効にします。SDKマネージャー-> [SDKツール]タブ->サポートリポジトリ-> ConstraintLayout for AndroidおよびSolver for 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を指定することです。これは、イメージの各ピクセルに使用されるバイト数を定義します。これはpreferredオプションであることに注意してください。ソースイメージの色がそれ以上ある場合でも、別の設定でデコードされます。

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

    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 Developer Web Site から:

最後の手段としてnotifyDataSetChanged()に依存してください。

ただし、使用する必要がある場合は、一意のIDでアイテムをメンテナンスしてください:

    adapter.setHasStableIds(true);

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

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

177
Galya
13
waclaw

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

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

画像のキャッシュを作成すると、驚くほどうまくいきました。 onBindViewHolder()は、キャッシュされた画像を最初から読み込むのではなく、キャッシュされた画像への参照を変更するだけです。これで、RecyclerViewが圧縮されます。

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

10
Scott Biggs

@Galyaの詳細な回答に加えて、最適化の問題である可能性がありますが、デバッガーを有効にすると、処理が大幅に遅くなる可能性があることも述べたいと思います。

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

私のアプリはdebugビルドバリアントでゆっくりと動作していましたが、releaseバリアントに切り替えるとすぐに動作しました。これはreleaseビルドバリアントを使用して開発する必要があるという意味ではありませんが、アプリを出荷する準備ができているときはいつでも正常に機能することを知っておくと便利です。

10
blastervla

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

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

enter image description here

フレームタイムは非常に長く、1秒あたりのフレームは非常に低かった。 :Sをレンダリングするのに約2秒必要なフレームがいくつかあることがわかります。

この悪いフレーム時間/ fpsの原因を解明しようとすると、ここにあるようにメモリの問題があることがわかりました:

enter image description here

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

それが私がUIの問題を発見した方法です。アプリでメモリリークが発生し、多くのガベージコレクタイベントが発生し、Android VMがメモリを1フレームごとに収集するためにアプリを停止しなければならなかったため、UIのパフォーマンスが低下していました。

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

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

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

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

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

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のコンテンツが変更されても同じままになるため、Item.id()は機能しません。
    PS DiffUtilを使用している場合、これは必要ありません!

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

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

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

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

詳細な説明については slides をご覧ください。

5
Oleksandr

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

2
saintjab

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

2
Chan Teck Wei

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

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

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

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

1
Roar Grønmo

すでに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 Please で常に確認できます。

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

1
Joel

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

1
Sami Adam

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

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

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

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

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

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

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

詳細については、 this Answer を参照してください。

1
mohandes

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

recyclerView.setNestedScrollingEnabled(false);
0
eLi