RecyclerView: Inkonsistenz erkannt. Ungültige Artikelposition


271

Unsere Qualitätssicherung hat einen Fehler festgestellt: Beim Drehen des Android-Geräts (Droid Turbo) ist der folgende Absturz im Zusammenhang mit RecyclerView aufgetreten :

java.lang.IndexOutOfBoundsException: Inkonsistenz festgestellt. Ungültige Position 2 (Versatz: 2) .Zustand: 3

Für mich sieht es nach einem internen Fehler in RecyclerView aus, da ich mir keine Möglichkeit vorstellen kann, dass dies direkt durch unseren Code verursacht wird ...

Ist jemand auf dieses Problem gestoßen?

Was wäre die Lösung?

Eine brutale Problemumgehung könnte darin bestehen, die Ausnahme zu erfassen und die RecyclverView-Instanz von Grund auf neu zu erstellen, um zu vermeiden, dass ein beschädigter Status verbleibt.

Aber wenn möglich, möchte ich das Problem besser verstehen (und es vielleicht an der Quelle beheben), anstatt es zu maskieren.

Der Fehler ist nicht leicht zu reproduzieren, aber er ist fatal, wenn er auftritt.

Die vollständige Stapelverfolgung:

W/dalvikvm( 7546): threadid=1: thread exiting with uncaught exception (group=0x41987d40)
    E/AndroidRuntime( 7546): FATAL EXCEPTION: main
    E/AndroidRuntime( 7546): Process: com.oblong.mezzedroid, PID: 7546
    E/AndroidRuntime( 7546): java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:3
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3382)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3340)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1810)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1306)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1269)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:523)
    E/AndroidRuntime( 7546):    at org.liboid.recycler_view.RecyclerViewContainer$LiLinearLayoutManager.onLayoutChildren(RecyclerViewContainer.java:179)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1942)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2237)
    E/AndroidRuntime( 7546):    at org.liboid.recycler_view.LiRecyclerView.onLayout(LiRecyclerView.java:30)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at com.oblong.mezzedroid.workspace.content.bins.BinsContainerLayout.onLayout(BinsContainerLayout.java:22)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2132)
    E/AndroidRuntime( 7546):    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1872)
    E/AndroidRuntime( 7546):    at andro

2
Eine Frage: Wie konsequent ist Ihr Repro? Ich weiß, dass dies hier und hier ein Fehler im Google-Code ist . Dies kann jedoch vermieden werden. Passiert das also bei jeder Drehung?
VicVu

Hallo. Es kommt nur selten vor, aber wenn es passiert, ist es für die App fatal.
KarolDepka

Danke für die Links zu den Bugs. Der erste scheint relevanter zu sein als der zweite.
KarolDepka

1
Ja, ich denke, Ihre beste Wette ist es, während der Rotation keine Änderungen an der Listenansicht zuzulassen.
VicVu

1
Wenn Sie leicht reproduzieren könnten, würde ich vorschlagen, den Wert für 'getItemCount' vor allen Aufrufen von 'notify *' zu drucken. Möglicherweise stellen Sie fest, dass Ihre Artikelanzahl nicht Ihren Annahmen entspricht.
Rich Ehmer

Antworten:


209

Ich hatte ein (möglicherweise) verwandtes Problem - das Eingeben einer neuen Instanz einer Aktivität mit einem RecyclerView, aber mit einem kleineren Adapter löste diesen Absturz für mich aus.

RecyclerView.dispatchLayout()kann versuchen, Gegenstände aus dem Schrott zu ziehen, bevor Sie anrufen mRecycler.clearOldPositions(). Die Folge ist, dass Elemente aus dem gemeinsamen Pool gezogen wurden, deren Positionen höher als die Adaptergröße waren.

Glücklicherweise funktioniert dies nur, wenn PredictiveAnimationses aktiviert ist. Meine Lösung bestand also darin, eine Unterklasse zu erstellen GridLayoutManager( LinearLayoutManagerhat das gleiche Problem und das gleiche Problem) und zu überschreiben supportsPredictiveItemAnimations(), um false zurückzugeben:

/**
 * No Predictive Animations GridLayoutManager
 */
private static class NpaGridLayoutManager extends GridLayoutManager {
    /**
     * Disable predictive animations. There is a bug in RecyclerView which causes views that
     * are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
     * adapter size has decreased since the ViewHolder was recycled.
     */
    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }

    public NpaGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public NpaGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public NpaGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }
}

4
Dies hat bei mir funktioniert, und das Deaktivieren von Vorhersageanimationen führt nicht dazu, dass Sie die Animationen insgesamt verlieren. Bravo.
Robert Liberatore

8
Vielen Dank, mein Herr! Arbeitete sofort mit LinearLayoutManager und sparte mir wahrscheinlich Tage.
Levavare

8
Vielen Dank. Diese Lösung wird mit LinearLayoutManager verwendet.
Pruthviraj

8
Ich denke, dieser Typ hat es verdient, dass wir zu Ehren seiner wertvollen Hilfe eine Statue bauen ... Es ist eines der am schlechtesten dokumentierten Probleme im Internet, aber es scheint, dass viele Entwickler auf dieses Problem stoßen ... Ich frage mich nur, wie Sie es gefunden haben übersprungen werden, wenn PredictiveAnimations falsch sind, @KasHunt? Weil die Stapelverfolgung sehr unklar ist ...
PAD

4
Weiß jemand, wie man das ohne diesen Hack behebt? Weil notifyDatasetChanged zugunsten von DiffUtil
Anton Shkurenko am

83

In meinem Fall (Daten in meine Datenstruktur löschen / einfügen) musste ich den Recycling-Pool löschen und dann den geänderten Datensatz benachrichtigen!

mRecyclerView.getRecycledViewPool().clear(); mAdapter.notifyDataSetChanged();


6
Normalerweise sage ich das nicht, ABER VIELEN DANK. Ich habe ALLES versucht, um diesen Absturz zu beheben, der sporadisch auftritt, wenn ich eine Reihe von Elementen auf der Liste in schneller Reihenfolge verschiebe. Ich habe buchstäblich wahrscheinlich eine ganze Woche damit verbracht, dieses Problem zu lösen. Ich ging ein paar Monate davon weg, um meinem Gehirn die Möglichkeit zu geben, es anders anzugehen, und fand dies dann bei meinem ersten Google-Versuch. Gesundheit!
Chantell Osejo

Lassen Sie mich Ihnen eine Menge Kekse besorgen, denn Sie haben jeden einzelnen verdient. Danke dir.
Antonis_st

warum musst du das machen
Dabluck

9
Das ist eine ziemlich schwere Operation, die den Zweck des Recyclings von Ansichten zunichte macht.
Gjsalot

@gjsalot Wenn ich das benutze, kann es dann einige Probleme verursachen?
Sreekanth Karumanaghat

38

Verwenden Sie notifyDataSetChanged()stattdessen notifyItem...in diesem Fall.


5
In einigen Fällen ist dies der richtige Weg. Ich hatte eine Situation, in der ich alle meine Elemente ausgetauscht habe, aber ich war nicht ehrlich mit dem Adapter. Ich habe nur gesagt, dass ich einige neue Elemente eingefügt habe (notifyItemRangeInserted), ohne vorher zu sagen, dass ich auch Elemente entfernt habe. Der Adapter erwartete dann, dass es mehr Elemente geben würde, als tatsächlich vorhanden waren. Wenn bei Verwendung einer der Benachrichtigungsmethoden des Adapters notifyDataSetChanged erwartet wird, z. B. notifyItemRangeRemoved / Inserted / Updated, hat der Aufrufer die volle Verantwortung, dem Adapter genau mitzuteilen , was geändert wurde, oder Sie erhalten möglicherweise diesen "inkonsistenten Status".
JHH

19
Dies ist überhaupt keine Lösung.
Miha_x64

Dies ist nicht der richtige Weg. Wenn dies funktioniert, bedeutet dies, dass Sie nur den Bereich durcheinander gebracht notifyItem...und korrigiert haben, der funktioniert, anstatt alle Elemente neu zu rendern.
Ranjan

12

Ich löste dieses Problem, indem ich die mRecycler.setAdapter(itemsAdapter)Kasse verzögerte, nachdem ich alle Elemente zum Adapter hinzugefügt hatte, mRecycler.addAll(items)und es funktionierte. Keine Ahnung, warum ich das zuerst getan habe. Aus dem Code einer Bibliothek habe ich diese Zeilen in der "falschen Reihenfolge" durchgesehen. Ich bin mir jedoch ziemlich sicher, dass dies der Fall ist. Bitte, wenn jemand dies bestätigen kann, erklären Sie, warum dies der Fall ist so? Ich bin mir nicht sicher, ob dies überhaupt eine gültige Antwort ist


Ich denke, dies ist die Lösung. Nachdem ich den Adapter verzögert habe, war es meiner Meinung nach in Ordnung. Jetzt wird er angezeigt, wenn der Adapter im UI-Thread festgelegt und Elemente hinzugefügt werden.
EngineSense

18
Ich habe swapAdapter(adapter, true)stattdessen verwendet setAdapter(adapter)und es hat geholfen.
Frangulyan

11

Ich hatte ein ähnliches Problem, aber nicht genau das gleiche. In meinem Fall habe ich an einem Punkt das Array gelöscht, das an die Recycling-Ansicht übergeben wurde

mObjects.clear();

und notifyDataSetChanged nicht aufrufen, da ich nicht wollte, dass die recyclerview die Ansichten sofort löscht. Ich habe das mObjects-Array in AsyncTask neu gefüllt.


9

Ich hatte das gleiche Problem mit recyclerView. Daher habe ich den Adapter direkt nach dem Löschen der Liste über die Änderung des Datensatzes informiert.

mList.clear();
mAdapter.notifyDataSetChanged();

mList.addAll(newData);
mAdapter.notifyDataSetChanged();

1
Dieser einfache Fehler hat mich so viel Zeit verlieren lassen, vielen Dank!
leb1755

7

Ich habe das gleiche Problem. Es trat auf, als ich schnell scrollte und die API aufrief und Daten aktualisierte. Nachdem ich alles versucht hatte, um einen Absturz zu verhindern, fand ich eine Lösung.

mRecyclerView.stopScroll();

Es wird klappen.


Dies ist eine Problemumgehung, keine Lösung. Sie zwingen es, die Schriftrolle anzuhalten. Bad UX
Dr. aNdRO

1
@ Dr.aNdRO: Der Adapter muss die Position festlegen. Wenn Sie weiter durch die Recylerview scrollen, kann der Adapter keine Daten festlegen, die die Ursache für den Absturz sind. Es ist nicht schlecht UX
Anand Savjani

1
Sinn ergeben. Das Stoppen des Bildlaufs ist nicht schlecht, da eine Datenaktualisierung stattfindet.
Sush

6

Ich ändere Daten für die RecyclerViewim Hintergrund Thread. Ich habe das gleiche Exceptionwie das OP. Ich habe dies nach dem Ändern der Daten hinzugefügt:

myRecyclerView.post(new Runnable() {
    @Override
    public void run() {
        myRecyclerAdapter.notifyDataSetChanged();
    }
});

Ich hoffe es hilft


Danke, Mann! Dies ist die einzige Antwort, die aus Sicht der Android-Entwicklung sinnvoll ist.
user347187

Während ich auch mit Hilfe von gelöst habe view.recycler_view.post, habe ich verwendet notifyItemInserted. In meinem Fall war es bereits ein UI-Thread.
CoolMind

6

Dieser Fehler tritt auf, wenn die Liste im Adapter beim Scrollen durch den Benutzer gelöscht wird, wodurch sich die Position des Artikelhalters ändert, die Referenz zwischen Liste und Artikel auf der Benutzeroberfläche verloren geht. Bei der nächsten Anforderung "notifyDataSetChanged" tritt ein Fehler auf .

Fix:

Überprüfen Sie Ihre Update-Listenmethode. Wenn du so etwas machst

mainList.clear();
...
mainList.add() or mainList.addAll()
...
notifyDataSetChanged();

===> Error occur

Wie repariert man. Erstellen Sie ein neues Listenobjekt für die Pufferverarbeitung und weisen Sie es anschließend erneut der Hauptliste zu

List res = new ArrayList();
…..
res.add();  //add item or modify list
….
mainList = res;
notifyDataSetChanged();

Vielen Dank an Nhan Cao für diese tolle Hilfe :)


4

Mein Problem wurde behoben, nachdem ich meine AdapterImplementierung so geändert hatte , dass anstelle einer Referenz eine Kopie des Elements-Arrays verwendet wurde. Die setItems()Methode wird jedes Mal aufgerufen, wenn neue Elemente in der angezeigt werden RecyclerView.

Anstatt:

private class MyAdapter extends RecyclerView.Adapter<ItemHolder> {
     private List<MyItem> mItems;  

    (....)

    void setItems(List<MyItem> items) {
        mItems = items;
    }
}

Ich tat:

void setItems(List<MyItem> items) {
    mItems = new ArrayList<>(items);
}

Dies wird das Problem lösen, aber wird es nicht doppelt so viel Speicherplatz beanspruchen?
Sreekanth Karumanaghat

@ MiguelA.Gabriel wird dies die Leistung beeinflussen? In meinem Fall aktualisiere ich beispielsweise das Array von recylerview zu häufig. Derzeit mache ich dies suggestionsRecyclerView.swapAdapter(new CandidatesAdapter(mSuggestions), true); und dies ist mein Konstruktor public CandidatesAdapter(List<String> suggestionsList) { this.suggestionsList = new ArrayList<>(suggestionsList); }
Mateen Chaudhry,

@ mateen-chaudhry Es wird wahrscheinlich. Sie müssen es in Ihrem Fall testen und entscheiden oder versuchen, eine andere der vorgeschlagenen Lösungen zu verwenden. Wie gesagt, es ist nur eine Problemumgehung und funktioniert in meinem Fall für mich.
Miguel A. Gabriel

3

Ich habe mit der gleichen Situation konfrontiert. Und es wurde durch Hinzufügen von Codes gelöst, bevor Sie Ihre Sammlung löschen.

mRecyclerView.getRecycledViewPool().clear();


3

In meinem Fall habe ich die Elemente aktualisiert und notifyDataSetChangedeinen Nicht-UI-Thread aufgerufen. Die meiste Zeit hat es funktioniert, aber wenn viele Änderungen schnell vorgenommen wurden, stürzte es ab. Als ich es stattdessen tat, im Grunde

activity.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        changeData();
        notifyDataSetChanged();
    }
});

dann hörte es auf zu krachen.


3

Sie müssen nur Ihre Liste löschen OnPostExecute()und nicht währenddessenPull to Refresh

// Setup refresh listener which triggers new data loading
        swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {

                AsyncTask<String,Void,String> task = new get_listings();
                task.execute(); // clear listing inside onPostExecute

            }
        });

Ich habe festgestellt, dass dies passiert, wenn Sie während eines Pulls zum Aktualisieren scrollen , da ich die Liste vor dem async taskgelöscht habe, was zu java.lang.IndexOutOfBoundsException: Inconsistency detected.

        swipeContainer.setRefreshing(false);
        //TODO : This is very crucial , You need to clear before populating new items 
        listings.clear();

Auf diese Weise werden Sie nicht mit einer Inkonsistenz enden


2

Dies kann auch mit dem mehrmaligen gleichzeitigen Einstellen des Adapters zusammenhängen. Ich hatte eine Rückrufmethode, die 5-6 Mal gleichzeitig ausgelöst wurde, und ich stellte den Adapter in diesem Rückruf so ein, dass RecycledViewPool nicht mit all diesen Daten gleichzeitig umgehen konnte. Es ist eine fette Chance, aber du solltest es trotzdem besser ausprobieren.


1
ja gleiches problem .. aber lösung? Sie geben nur den Grund an .. Wie zu beheben?
Ranjith Kumar

@RanjithKumar, teilen Sie uns bitte Ihren Weg mit, um das oben genannte Problem zu lösen. Ich habe es mit mRecyclerView.getRecycledViewPool () gelöst. Clear (); vor notifyDataSetChanged und Verwendung eines synchronisierten Blocks um die Aktualisierungsfunktion des Adapters
Attiq ur Rehman


2

Verwenden

notifyDataSetChanged()

stattdessen

notifyItemRangeInserted(0, YourArrayList.size())

in diesem Fall.


1
aber das ist nicht gut für die Leistung, oder? notifyItemRangeInserted ist besser, das Problem liegt nicht hier
Derekyy

2

Um dieses Problem zu beheben, rufen Sie einfach notifyDataSetChanged () mit leerer Liste auf, bevor Sie die Recyclingansicht aktualisieren.

Beispielsweise

//Method for refresh recycle view

    if (!hcpArray.isEmpty())

hcpArray.clear (); // Die Liste für die Update-Recycling-Ansicht

adapter.notifyDataSetChanged();

2
Keine Lösung.
Miha_x64

@Milha Ich habe keine andere Lösung gefunden, um das Absturzproblem zu beheben. Aber die obige Lösung funktioniert für mich. Wenn es keine Lösung ist, teilen Sie mir die richtige Lösung mit.
EKN

Es hängt davon ab, ob. Sie können versuchen, DiffUtil zu verwenden - ein universelles Tool zum Aktualisieren von RecyclerView-Inhalten.
Miha_x64

2

In meinem Fall habe ich gerade die Zeile mit entfernt setHasStableIds(true);


Aber HasStableIds (true) verbessert die Leistung des Rv. Gibt es eine alternative Lösung?
Sreekanth Karumanaghat

Eigentlich denke ich, dass es verschiedene Gründe haben könnte, also könnte es verschiedene Lösungen für dieses Problem geben, basierend auf der Hauptursache.
Sreekanth Karumanaghat

2

In meinem Fall habe ich versucht, den Inhalt meines Adapters in einem Hintergrundthread zu ändern, aber im Haupt- / UI-Thread notify * aufgerufen.

Das ist nicht möglich! Der Grund, warum die Benachrichtigung zum Hauptthread gezwungen wird, besteht darin, dass die Recycling-Ansicht möchte, dass Sie Ihren Sicherungsadapter im Hauptthread bearbeiten, selbst auf demselben Aufrufstapel.

Um das Problem zu lösen, stellen Sie sicher, dass jeder Vorgang an Ihrem Adapter sowie jede Benachrichtigung ... über den UI / Main-Thread ausgeführt wird !


2
Das Hinzufügen von Elementen zur Liste in Ihrem Adapter sollte in einem Hintergrundthread erfolgen und bei postexecute eine Benachrichtigung aufrufen. Durch das Hinzufügen von Daten im UI-Thread friert die App für einige Millisekunden oder Sekunden ein, wenn viele Daten hinzugefügt werden
dione llorera

In Übereinstimmung mit @dionellorera sollte klargestellt werden, dass eine "Änderung des Adapterinhalts" speziell das direkte Ändern von Daten bedeutet, unabhängig davon, ob es sich um primitive Werte, Eigenschaften von Objekten oder Objekte selbst handelt
OzzyTheGiant,

2

Ich bin kürzlich mit den neuen Android-Architekturkomponenten auf diesen fiesen Stack-Trace gestoßen. Im Wesentlichen habe ich eine Liste von Elementen in meinem ViewModel, die von meinem Fragment mithilfe von LiveData beobachtet werden. Wenn das ViewModel einen neuen Wert für die Daten veröffentlicht, aktualisiert das Fragment den Adapter, übergibt diese neuen Datenelemente und benachrichtigt den Adapter über Änderungen.

Leider konnte ich bei der Übergabe der neuen Datenelemente an den Adapter nicht berücksichtigen, dass sowohl das ViewModel als auch der Adapter auf dieselbe Objektreferenz verweisen würden! Das heißt, wenn ich die Daten aktualisiere und anrufepostValue() aus dem ViewModel heraus anrufe, gibt es ein sehr kleines Fenster, in dem die Daten aktualisiert und der Adapter noch nicht benachrichtigt werden kann!

Mein Fix bestand darin, eine neue Kopie der Elemente zu instanziieren, wenn sie an den Adapter übergeben wurden:

mList = new ArrayList<>(passedList);

Mit dieser supereinfachen Lösung können Sie sicherstellen, dass sich Ihre Adapterdaten erst unmittelbar vor der Benachrichtigung Ihres Adapters ändern.


2

Dies ist nur eine Lösung, die für mich funktioniert hat, selbst wenn ich viele der oben genannten Lösungen ausprobiert habe.

1.) Intilisierung

CustomAdapter scrollStockAdapter = new CustomAdapter(mActivity, new ArrayList<StockListModel>());
list.setAdapter(scrollStockAdapter);
scrollStockAdapter.updateList(stockListModels);

2.) Schreiben Sie diese Methode in den Adapter

public void updateList(List<StockListModel> list) {
stockListModels.clear();
stockListModels.addAll(list);
notifyDataSetChanged();
}

stockListModels -> Diese Liste verwenden Sie im Adapter.


2

Bei mir hat es nach dem Hinzufügen dieser Codezeile funktioniert:

mRecyclerView.setItemAnimator(null);

2
Dies ist in den meisten Fällen keine Lösung. Wenn Sie eine Animation wünschen, müssen Sie Ihren Adaptercode neu schreiben und Ihre Fehler bei der Benachrichtigung über Änderungen
feststellen

Es funktioniert gut für mich. Ich verwende einen echten Adapter, damit ich den Fluss nicht steuern kann, und ich habe windowActivityTransitions in einem Stil aktiviert, der dieses Problem verursacht, danke Mann, Sie retten meinen Tag.
Arul Mani

1

Dieses Problem kann auftreten, wenn Sie versuchen, Ihre Liste zu löschen. Wenn Sie Ihre Datenliste löschen möchten, insbesondere wenn Sie Pull zum Aktualisieren verwenden, versuchen Sie, ein boolesches Flag zu verwenden, initialisieren Sie es als false und löschen Sie Ihre Datenliste innerhalb der OnRefresh-Methode auf true Wenn das Flag wahr ist, kurz bevor Sie die neuen Daten hinzufügen und danach falsch machen.

Ihr Code könnte so sein

 private boolean pullToRefreshFlag = false ;
 private ArrayList<your object> dataList ;
 private Adapter adapter ;

 public class myClass extend Fragment implements SwipeRefreshLayout.OnRefreshListener{

 private void requestUpdateList() {

     if (pullToRefresh) {
        dataList.clear
        pullToRefreshFlag = false;
     }

     dataList.addAll(your data);
     adapter.notifyDataSetChanged;


 @Override
 OnRefresh() {
 PullToRefreshFlag = true
 reqUpdateList() ; 
 }

}

1

Ich hatte vorher das gleiche Problem. Endlich eine Problemumgehung dafür gefunden

Was ich tue, ist, den Adapter zu benachrichtigen, dass das Element entfernt wurde, und dann den geänderten Bereich des Adapterdatensatzes zu benachrichtigen

 public void setData(List<Data> dataList) {
      if (this.dataList.size() > 0) {
          notifyItemRangeRemoved(0, dataList.size());
          this.dataList.clear();
      }
      this.dataList.addAll(dataList)
      notifyItemRangeChanged(0, dataList.size());

 }

1

Ich bin auf ein ähnliches Problem gestoßen und habe es gerade herausgefunden. Ich habe einige Beispiele für einen Testfall fest codiert, aber nicht sichergestellt, dass sie jeweils eine eindeutige ID zurückgeben. Dies hat den folgenden Absturz für mich verursacht. Durch das Beheben der IDs wurde das Problem behoben. Ich hoffe, dies hilft jemand anderem!


1

Ich habe auch einmal den Fehler bekommen:

Ursache: Ich habe versucht, eine Recycler-Ansicht über eine asynchrone Aufgabe zu aktualisieren, während gleichzeitig versucht wurde, alte gelöschte viewHolders abzurufen.

Code: Ich generiere Daten auf Knopfdruck, Logik wie folgt

  1. Löschen Sie die letzten Elemente in der Recycler-Ansicht
  2. Rufen Sie die asynchrone Aufgabe auf, um Daten zu generieren
  3. OnPostExecute Aktualisieren Sie die Recycler-Ansicht und NotifyDataSetChanged

Problem: Immer wenn ich schnell scrolle, bevor ich meine Daten generiere, bekomme ich

Inkonsistenz festgestellt. Ungültiger Ansichtshalteradapter positionViewHolder java.lang.IndexOutOfBoundsException: Inkonsistenz festgestellt. Ungültige Position 20 (Versatz: 2) .Zustand: 3

Lösung: Anstatt die RecyclerView vor dem Generieren meiner Daten zu löschen, lasse ich sie stattdessen und ersetze sie durch die neuen Daten, den Aufruf NotifyDatasetChanged, wie unten gezeigt.

       @Override
        protected void onPostExecute(List<Objects> o) {
            super.onPostExecute(o);
            recyclerViewAdapter.setList(o);
            mProgressBar.setVisibility(View.GONE);
            mRecyclerView.setVisibility(View.VISIBLE);
        }

Kannst du bitte einen Blick auf meinen Code werfen? Ich denke, mein Problem ist wie dein [Link] ( stackoverflow.com/questions/50213362/… )
Mateen Chaudhry

1

Entfernen Sie einfach alle Ansichten Ihres Layout-Managers, bevor Sie benachrichtigt werden. mögen:

myLayoutmanager.removeAllViews();

Es klappt. Ich hatte ein Problem mit dem Laden des Bildlaufs und dem Ändern der Registerkarte.
Warwicky

1

Mit ListAdapter (androidx.recyclerview.widget.ListAdapter)Anruf adapter.submitList(null)vor dem Aufruf adapter.submitList(list):

adapter.submitList(null)
adapter.submitList(someDataList)

0

Ich fand diese Einstellung mRecycler.setLayoutFrozen (true); in der onRefresh-Methode des swipeContainer.

löste das Problem für mich.

swipeContainer.setOnRefreshListener(new   SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            orderlistRecycler.setLayoutFrozen(true);
            loadData(false);

        }
    });

0

Dies ist ein ziemlich böser Fehler.

Um meinen Artikelklick zu handhaben, habe ich eine Implementierung verwendet, die RecyclerView.OnItemTouchListenerder in dieser Frage gefundenen Lösung ähnelt .

Nach RecyclerViewmehrmaligem Aktualisieren der Datenquelle und Klicken auf ein Element IndexOutOfBoundsExceptionstürzte meine Anwendung ab. Wenn auf ein Element geklickt wird, RecyclerViewsucht das Element intern nach der richtigen zugrunde liegenden Ansicht und gibt die Position zurück. Beim Auschecken des Quellcodes sah ich, dass es einige gab TasksundThreads geplant war. Um es kurz zu machen, im Grunde ist es nur ein illegaler Zustand, in dem zwei Datenquellen vermischt und nicht synchronisiert werden und das Ganze wild wird.

Auf dieser Grundlage entfernte ich meine Implementierung von RecyclerView.OnItemTouchListenerund fing einfach den Klick auf das ViewHoldervon Adaptermir selbst auf:

public void onBindViewHolder (final BaseContentView holder, final int position) {

    holder.itemView.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick (View view) {

        // do whatever you like here
      }
    });

}

Dies ist möglicherweise nicht die beste Lösung, aber vorerst absturzfrei. Hoffentlich sparen Sie dadurch etwas Zeit :).


Das Erstellen eines neuen Objekts bei jedem Aufruf von onBind führt dazu, dass viele Objekte durch Müll gesammelt werden und der Benutzer möglicherweise einfriert.
Dephinera

0

Lint gab mir einen Rat bezüglich Inkonsistenz: Ich schrieb (onBindViewHolder ()):

pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        doStuff(position);
                    }
                });

die ersetzt werden musste durch:

pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        doStuff(pholder.getAdapterPosition());
                    }
                });

Führen Sie beide Codes in Ihrem Code aus und führen Sie dann Lint aus, um die vollständige Erklärung zu erhalten !!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.