wake-up-neo.com

RecyclerView-Scrolling-Leistung

Ich habe ein RecyclerView-Beispiel erstellt, das auf Erstellen von Listen und Karten guide basiert. Mein Adapter hat eine Musterimplementierung, um das Layout aufzublähen. 

Das Problem ist die schlechte Bildlaufleistung. Dies in einer RecycleView mit nur 8 Elementen.

In einigen Tests habe ich bestätigt, dass dieses Problem in Android L nicht auftritt. In der KitKat-Version ist der Leistungsabfall jedoch offensichtlich.

49
falvojr

Ich bin kürzlich mit demselben Problem konfrontiert worden, also habe ich dies mit der neuesten RecyclerView-Unterstützungsbibliothek gemacht:

  1. Ersetzen Sie ein komplexes Layout (verschachtelte Ansichten, RelativeLayout) durch das neue optimierte ConstraintLayout. Aktivieren Sie es in Android Studio: Gehen Sie zu SDK Manager -> SDK Tools - Registerkarte -> Support Repository -> prüfen Sie ConstraintLayout für Android und Solver für ConstraintLayout. Fügen Sie zu den Abhängigkeiten hinzu:

    compile 'com.Android.support.constraint:constraint-layout:1.0.2'
    
  2. Wenn möglich, machen Sie alle Elemente der RecyclerView mit der gleichen Höhe. Und füge hinzu:

    recyclerView.setHasFixedSize(true);
    
  3. Verwenden Sie die voreingestellten RecyclerView-Methoden drawing cache und passen Sie sie an Ihren Fall an. Sie benötigen dazu keine Bibliothek von Drittanbietern:

    recyclerView.setItemViewCacheSize(20);
    recyclerView.setDrawingCacheEnabled(true);
    recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
    
  4. Wenn Sie viele images verwenden, stellen Sie sicher, dass ihre Größe und Komprimierung optimal sind. Das Skalieren von Bildern kann auch die Leistung beeinträchtigen. Es gibt zwei Seiten des Problems - das verwendete Quellbild und die decodierte Bitmap. Das folgende Beispiel zeigt Ihnen, wie Sie ein aus dem Internet heruntergeladenes Bild decodieren können:

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

Der wichtigste Teil ist die Angabe von inPreferredConfig - es definiert, wie viele Bytes für jedes Pixel des Bildes verwendet werden. Beachten Sie, dass dies eine bevorzugte -Option ist. Wenn das Quellbild mehr Farben enthält, wird es dennoch mit einer anderen Konfiguration dekodiert.

  1. Stellen Sie sicher, dass onBindViewHolder () so cheap wie möglich ist. Sie können OnClickListener einmal in onCreateViewHolder() festlegen und über eine Schnittstelle einen Listener außerhalb des Adapters aufrufen, wobei Sie das angeklickte Element übergeben. Auf diese Weise erstellen Sie nicht ständig zusätzliche Objekte. Überprüfen Sie auch Flags und Status, bevor Sie hier Änderungen an der Ansicht vornehmen.

    viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              Item item = getItem(getAdapterPosition());
              outsideClickListener.onItemClicked(item);
          }
    });
    
  2. Wenn Daten geändert werden, versuchen Sie, nur die betroffenen Elemente zu aktualisieren. Wenn Sie beispielsweise nicht den gesamten Datensatz mit notifyDataSetChanged() ungültig machen, verwenden Sie beim Hinzufügen/Laden weiterer Elemente einfach Folgendes:

    adapter.notifyItemRangeInserted(rangeStart, rangeEnd);
    adapter.notifyItemRemoved(position);
    adapter.notifyItemChanged(position);
    adapter.notifyItemInserted(position);
    
  3. Von Android-Entwicklerwebsite :

Verlassen Sie sich als letzter Ausweg auf notifyDataSetChanged ().

Wenn Sie es jedoch benötigen, pflegen Sie Ihre Artikel mit unique ids:

    adapter.setHasStableIds(true);

RecyclerView versucht, sichtbare strukturelle Änderungen zu synthetisieren Ereignisse für Adapter, die melden, dass sie über stabile IDs verfügen, wenn diese Methode wird verwendet. Dies kann für Animations- und Visualisierungszwecke hilfreich sein Objektpersistenz, aber einzelne Elementansichten müssen immer noch __ sein. Rebound und verlegt.

Selbst wenn Sie alles richtig machen, funktioniert RecyclerView wahrscheinlich nicht so reibungslos, wie Sie möchten.

145
Galya
12
waclaw

Ich habe mindestens ein Muster entdeckt, das Ihre Leistung töten kann. Denken Sie daran, dass onBindViewHolder() häufig heißt. Alles, was Sie in diesem Code tun, hat das Potenzial, Ihre Leistung zum Stillstand zu bringen. Wenn Ihr RecyclerView Anpassungen vornimmt, ist es sehr leicht, versehentlich langsamen Code in diese Methode einzufügen.

Ich habe die Hintergrundbilder jedes RecyclerView in Abhängigkeit von der Position geändert. Das Laden von Bildern erfordert jedoch ein bisschen Arbeit, wodurch mein RecyclerView träge und ruckartig wird.

Das Erstellen eines Caches für die Bilder hat Wunder bewirkt. onBindViewHolder() ändert jetzt lediglich einen Verweis auf ein zwischengespeichertes Bild, anstatt es von Grund auf zu laden. Nun reißt der RecyclerView mit.

Ich weiß, dass nicht jeder dieses exakte Problem haben wird, also mache ich mir nicht die Mühe, Code zu laden. Berücksichtigen Sie jedoch alle Arbeiten, die in Ihrer onBindViewHolder() ausgeführt werden, als potenziellen Engpaß bei schlechter RecyclerView-Leistung.

9
Scott Biggs

Zusätzlich zu @ Galyas ausführlicher Antwort möchte ich darauf hinweisen, dass es zwar auch ein Optimierungsproblem sein kann, dass der Debugger jedoch aktiviert sein kann und die Dinge sehr verlangsamen kann. 

Wenn Sie alles tun, um Ihre RecyclerView zu optimieren, und es trotzdem nicht reibungslos funktioniert, wechseln Sie die Build-Variante zu release und prüfen Sie, wie sie in einer Nicht-Entwicklungsumgebung funktioniert (bei deaktiviertem Debugger).

Es ist mir passiert, dass meine App in debug build-Variante langsam lief, aber sobald ich zur release-Variante wechselte, funktionierte das reibungslos. Dies bedeutet nicht, dass Sie mit der Buildvariante release entwickeln sollten, aber es ist gut zu wissen, dass es immer gut funktioniert, wenn Sie bereit sind, Ihre App zu versenden.

7
blastervla

Ich bin nicht wirklich sicher, ob die Verwendung von setHasStableId Flag Ihr Problem behebt. Basierend auf den von Ihnen bereitgestellten Informationen könnte Ihr Leistungsproblem mit einem Speicherproblem zusammenhängen. Ihre Anwendungsleistung in Bezug auf Benutzeroberfläche und Arbeitsspeicher ist relativ eng miteinander verbunden.

Letzte Woche entdeckte ich, dass meine App Speicher löste. Ich entdeckte dies, weil ich nach 20 Minuten mit meiner App bemerkte, dass die Benutzeroberfläche wirklich langsam lief. Das Schließen/Öffnen einer Aktivität oder das Scrollen eines RecyclerView mit einer Reihe von Elementen war wirklich langsam. Nachdem ich einige meiner Benutzer in der Produktion mit http://flowup.io/ überwacht hatte, fand ich Folgendes:

 enter image description here

Die Frame-Zeit war wirklich sehr hoch und die Frames pro Sekunde sehr niedrig. Sie können sehen, dass einige Bilder für das Rendern etwa 2 Sekunden benötigten: S.

Beim Versuch herauszufinden, was diese schlechte Frame-Zeit/fps verursacht hat, stellte ich fest, dass ich ein Speicherproblem hatte, wie Sie hier sehen können:

 enter image description here

Selbst wenn der durchschnittliche Speicherverbrauch zur selben Zeit nahe bei 15 MB lag, löschte die App Frames.

So habe ich das Problem mit der Benutzeroberfläche entdeckt. Ich hatte ein Speicherleck in meiner App, das eine Menge Müllsammelereignisse verursachte. Dies führte zu einer schlechten Leistung der Benutzeroberfläche, da Android VM meine App stoppen musste, um Speicher für jeden einzelnen Frame zu sammeln.

Beim Betrachten des Codes hatte ich eine Lücke in einer benutzerdefinierten Ansicht, da die Registrierung eines Listeners von der Android Choreographer-Instanz nicht aufgehoben wurde. Nach der Veröffentlichung des Updates wurde alles normal :)

Wenn Ihre App aufgrund eines Speicherproblems Frames löscht, sollten Sie zwei häufig auftretende Fehler überprüfen:

Überprüfen Sie, ob Ihre App Objekte innerhalb einer Methode zuordnet, die mehrmals pro Sekunde aufgerufen wird. Auch wenn diese Zuordnung an einem anderen Ort erfolgen kann, an dem Ihre Anwendung langsam wird. Beispiel: Erstellen Sie neue Instanzen eines Objekts in einer benutzerdefinierten OnDraw-Ansichtsmethode auf onBindViewHolder in Ihrem Recycler-View-Ansicht .. .. Überprüfen Sie, ob Ihre App eine Instanz im Android SDK registriert, diese jedoch nicht freigibt. Das Registrieren eines Listeners in einem Busereignis könnte auch ein Leck sein.

Disclaimer: Das Tool, mit dem ich meine App überwacht habe, befindet sich in der Entwicklung. Ich habe Zugriff auf dieses Tool, weil ich einer der Entwickler bin :) Wenn Sie auf dieses Tool zugreifen möchten, werden wir bald eine Betaversion veröffentlichen! Sie können an unserer Website teilnehmen: http://flowup.io/ .

Wenn Sie verschiedene Tools verwenden möchten, die Sie verwenden können: Traveview, dmtracedump, systrace oder der in Android Studio integrierte Andorid-Leistungsmonitor. Denken Sie jedoch daran, dass diese Tools Ihr angeschlossenes Gerät und nicht die restlichen Benutzergeräte oder Android-Betriebssysteminstallationen überwachen.

Ich sprach über die Leistung von RecyclerView. Hier sind Folien in Englisch und Video in Russisch .

Es enthält eine Reihe von Techniken (einige davon sind bereits durch @ Darjas Antwort abgedeckt).

Hier ist eine kurze Zusammenfassung:

  • Wenn Adapter Artikel eine feste Größe haben, dann setze:
    recyclerView.setHasFixedSize(true);

  • Wenn Datenentitäten durch long dargestellt werden können (zum Beispiel hashCode()), dann setze:
    adapter.hasStableIds(true);
    und implementieren:
    // YourAdapter.Java
    @Override
    public long getItemId(int position) {
    return items.get(position).hashcode(); //id()
    }
    In diesem Fall würde Item.id() nicht funktionieren, da es auch dann gleich bleiben würde, wenn sich der Inhalt von Item geändert hat.
    P.S. Dies ist nicht erforderlich, wenn Sie DiffUtil verwenden!

  • Verwenden Sie eine richtig skalierte Bitmap. Erfinde das Rad nicht neu und benutze Bibliotheken.
    Weitere Informationen zur Auswahl hier .

  • Verwenden Sie immer die neueste Version von RecyclerView. Zum Beispiel gab es enorme Leistungsverbesserungen in 25.1.0 - Prefetch.
    Weitere Infos hier .

  • Verwenden Sie DiffUtill.
    DiffUtil ist ein Muss .
    Offizielle Dokumentation .

  • Vereinfachen Sie das Layout Ihres Artikels!
    Kleine Bibliothek zur Anreicherung von TextViews - TextViewRichDrawable

Weitere Informationen finden Sie unter Folien .

5
Oleksandr

Es ist auch wichtig, das übergeordnete Layout zu überprüfen, in das Sie Ihren Recyclerview einfügen. Beim Testen von recyclerView in einem nestedscrollview hatte ich ähnliche Probleme beim Scrollen. Eine Ansicht, die in einer anderen Ansicht einen Bildlauf ausführt und beim Bildlauf einen Bildlauf nach sich ziehen kann

2
saintjab

In meinem Fall habe ich herausgefunden, dass die nennenswerte Ursache für die Verzögerung das häufige Ziehen innerhalb der #onBindViewHolder()-Methode ist. Ich habe es gelöst, indem ich die Bilder als Bitmap einmal in den ViewHolder lade und von der genannten Methode darauf zugreife. Das ist alles was ich getan habe.

2
Chan Teck Wei

Dies hat mir geholfen, ein reibungsloseres Scrollen zu erreichen:

Überschreiben Sie den onFailedToRecycleView (ViewHolder-Halter) im Adapter

und stoppen Sie alle laufenden Animationen (falls vorhanden) Inhaber. "animateview" .clearAnimation ();

erinnere dich an return true;

1
Roar Grønmo

Ich sehe in den Kommentaren, dass Sie bereits das ViewHolder-Muster implementieren, aber ich werde hier einen Beispieladapter veröffentlichen, der das RecyclerView.ViewHolder-Muster verwendet, sodass Sie überprüfen können, ob Sie es auf ähnliche Weise integrieren. Ihr Konstruktor kann wiederum abhängig von Ihren Anforderungen variieren , Hier ist ein Beispiel:

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);
        }
    }
}

Wenn Sie Probleme mit RecyclerView.ViewHolder haben, stellen Sie sicher, dass Sie die entsprechenden Abhängigkeiten haben, die Sie immer unter Gradle Please überprüfen können.

Hoffe, es löst dein Problem.

1
Joel

Zu @ Galyas Antwort habe ich in bind viewHolder die Methode Html.fromHtml () verwendet. anscheinend hat dies Auswirkungen auf die Leistung.

1
Sami Adam

In meinem Fall habe ich komplexe Recycling-Kinder. Es beeinflusste also die Aktivitätsladezeit (~ 5 Sekunden für Aktivitäts-Rendering)

Ich lade den Adapter mit postDelayed () -> das ergibt ein gutes Ergebnis für das Aktivitäts-Rendering. Nach der Aktivität wird mein Recyclerview mit Smooth geladen.

Versuchen Sie diese Antwort,

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

In meiner RecyclerView verwende ich Bitmap Images als Hintergrund für mein item_layout.
Alles, was @Galya gesagt hat, ist wahr (und ich danke ihm für seine großartige Antwort). Aber sie haben nicht für mich gearbeitet.

Das hat mein Problem gelöst:

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

Für weitere Informationen lesen Sie bitte diese Antwort .

1
mohandes

Ich habe es durch diese Codezeile gelöst

recyclerView.setNestedScrollingEnabled(false);
0
eLi