Android, ListView IllegalStateException: “Der Inhalt des Adapters hat sich geändert, aber ListView hat keine Benachrichtigung erhalten”

Lesezeit: 13 Minuten

Android ListView IllegalStateException Der Inhalt des Adapters hat sich geandert
tomasch

Was ich machen will; was ich vorhabe zu tun: Führen Sie einen Hintergrund-Thread aus, der den ListView-Inhalt berechnet, und aktualisieren Sie ListView teilweise, während die Ergebnisse berechnet werden.

Was ich weiß, muss ich vermeiden: Ich kann nicht mit ListAdapter-Inhalten aus dem Hintergrundthread herumspielen, also habe ich AsyncTask geerbt und das Ergebnis (Einträge zum Adapter hinzufügen) von onProgressUpdate veröffentlicht. Mein Adapter verwendet ArrayList von Ergebnisobjekten, alle Operationen auf diesen Arraylisten werden synchronisiert.

Recherche anderer Personen: Es gibt sehr wertvolle Daten Hier. Ich litt auch unter fast täglichen Abstürzen für eine Gruppe von ~ 500 Benutzern, und als ich hinzufügte list.setVisibility(GONE)/trackList.setVisibility(VISIBLE) Block in onProgressUpdate, Abstürze um den Faktor 10 gesenkt, aber nicht verschwunden. (es wurde vorgeschlagen in Antworten )

Was ich manchmal bekam: Bitte beachten Sie, dass dies sehr selten vorkommt (einmal pro Woche für einen von 3,5.000 Benutzern). Aber ich würde diesen Fehler gerne komplett loswerden. Hier ist ein partieller Stacktrace:

`java.lang.IllegalStateException:` The content of the adapter has changed but ListView  did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131296334, class android.widget.ListView) with Adapter(class com.transportoid.Tracks.TrackListAdapter)]
at android.widget.ListView.layoutChildren(ListView.java:1432)
at android.widget.AbsListView.onTouchEvent(AbsListView.java:2062)
at android.widget.ListView.onTouchEvent(ListView.java:3234)
at android.view.View.dispatchTouchEvent(View.java:3709)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:852)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:884)
[...]

Hilfe? Wird nicht mehr benötigt, siehe unten

ENDGÜLTIGE ANTWORT: Wie sich herausstellte, rief ich an notifyDataSetChanged alle 5 Einfügungen, um Flackern und plötzliche Listenänderungen zu vermeiden. Es ist nicht möglich, den Adapter immer zu benachrichtigen, wenn sich die Basisliste ändert. Dieser Bug ist bei mir schon lange vorbei.

  • Haben Sie “notifyDataSetChanged()” aufgerufen?

    – Denis Palnitsky

    28. Juni 2010 um 11:49 Uhr

  • Natürlich gibt es in onProgressUpdate eine Sequenz: list.setVisibility(GONE) – addObjectToList/synchronized operation on list/ – notificationDataSetChanged() – list.setVisibility(VISIBLE) (und ohne Sichtbarkeitsänderungen tritt die Ausnahme weitaus häufiger auf)

    – tomasch

    28. Juni 2010 um 11:55 Uhr


  • Ändern Sie die zugrunde liegende ArrayList in einem anderen Thread? Alle Änderungen, auch an der vom Adapter referenzierten ArrayList, müssen im UI-Thread erfolgen.

    – Rich Schuler

    29. Juni 2010 um 3:29 Uhr

  • @Qberticus – wie ich klar gesagt habe, ändere ich ArrayList nicht aus einem anderen Thread, sondern aus der Methode onProgressUpdate von AcyncTask – es funktioniert im GUI-Thread.

    – tomasch

    29. Juni 2010 um 5:31 Uhr

  • @tomash Ich habe die gleiche Ausnahme, die ich zum Aktualisieren verwende, und in der Postausführung, die ich geschrieben habe adapter.notifyDataSetChanged(); lvAutherlist.completeRefreshing(); aber manchmal bekam ich diesen Fehler, wie man ihn löst

    – Khan

    21. Januar 2013 um 8:00 Uhr

1646319794 612 Android ListView IllegalStateException Der Inhalt des Adapters hat sich geandert
Mullins

Ich hatte das gleiche Problem.

Ich habe Artikel zu meinem hinzugefügt ArrayList außerhalb des UI-Threads.

Lösung: Ich habe beides gemacht, adding the items und angerufen notifyDataSetChanged() im UI-Thread.

  • Ich habe die Elemente hinzugefügt und im UI-Thread alertDataSetChanged() aufgerufen und das Problem gelöst.

    – Mullins

    12. Dezember 2013 um 11:52 Uhr

  • Genialer Kommentar, wirklich.

    – Zahnfleisch

    16. Dezember 2013 um 8:08 Uhr

  • Dies ist ein Multithreading-Problem und die Verwendung ordnungsgemäß synchronisierter Blöcke. Dies kann verhindert werden. Ohne zusätzliche Dinge in den UI-Thread zu legen und die Reaktionsfähigkeit der App zu beeinträchtigen. Überprüfen Sie meine Antwort unten.

    – Rohit Sharma

    2. Januar 2014 um 7:50 Uhr

  • Für zukünftige Leser: Das Ausführen von Berechnungen im Hintergrundthread ist eine gute Sache, aber Sie sollten die Ergebnisse an den UI-Thread übergeben und das Hinzufügen von Elementen zur Adapterbasissammlung und das Benachrichtigen des Adapters im selben Codeblock durchführen.

    – tomasch

    22. April 2014 um 8:09 Uhr

  • @Mullins Kannst du bitte ein Beispiel für deine Lösung zeigen? Ich stecke bei demselben Problem fest

    – Ja

    19. Oktober 2015 um 6:40 Uhr


1646319795 475 Android ListView IllegalStateException Der Inhalt des Adapters hat sich geandert
gian1200

Ich hatte das gleiche Problem, aber ich habe es mit der Methode behoben

requestLayout();

aus der Klasse ListView

  • Wann sollten wir diese Methode aufrufen?

    – JehandadK

    19. Februar 2013 um 12:59 Uhr

  • @DeBuGGeR nachdem Sie entweder Elemente zur ListView hinzugefügt oder den Adapter geändert haben.

    – Ahmet Noyan Kızıltan

    15. Juni 2013 um 22:34 Uhr


  • Was ist, wenn ich Elemente in Asynctask hinzufüge?

    – Dr. ANDRO

    7. März 2014 um 5:46 Uhr

  • Nur fürs Protokoll, @Dr.aNdRO, Sie sammeln Ihre Ergebnisse in AsyncTask. doInBackground() und speichern Sie Ihre Ergebnisse, um die Liste in AsyncTask.onPostExecute() zu aktualisieren, die im UI-Thread ausgeführt wird. Wenn du brauchen Um unterwegs zu aktualisieren, verwenden Sie AsyncTask.publishProgress() und AsyncTask.onProgressUpdate(), die auch im UI-Thread ausgeführt werden.

    – Nicole Borelli

    13. August 2014 um 18:18 Uhr

Android ListView IllegalStateException Der Inhalt des Adapters hat sich geandert
Rohit Sharma

Das ist ein Multithreading Ausgabe und ordnungsgemäße Verwendung Synchronisiert Sperren Dies kann verhindert werden. Ohne zusätzliche Dinge in den UI-Thread zu legen und die Reaktionsfähigkeit der App zu beeinträchtigen.

Ich stand auch vor dem gleichen. Und wie die am meisten akzeptierte Antwort vorschlägt, kann das Problem durch eine Änderung der Adapterdaten aus dem UI-Thread gelöst werden. Das wird funktionieren, ist aber eine schnelle und einfache Lösung, aber nicht die beste.

Wie Sie für einen normalen Fall sehen können. Das Aktualisieren des Datenadapters aus dem Hintergrund-Thread und das Aufrufen von “notifyDataSetChanged” im UI-Thread funktioniert.

Diese illegalStateException tritt auf, wenn ein ui-Thread die Ansicht aktualisiert und ein anderer Hintergrund-Thread die Daten erneut ändert. Dieser Moment verursacht dieses Problem.

Wenn Sie also den gesamten Code synchronisieren, der die Adapterdaten ändert und den Aufruf “notifydatasetchange” durchführt. Dieses Problem sollte weg sein. Für mich weg und ich aktualisiere immer noch die Daten aus dem Hintergrundthread.

Hier ist mein fallspezifischer Code, auf den sich andere beziehen können.

Mein Loader auf dem Hauptbildschirm lädt im Hintergrund die Telefonbuchkontakte in meine Datenquellen.

    @Override
    public Void loadInBackground() {
        Log.v(TAG, "Init loadings contacts");
        synchronized (SingleTonProvider.getInstance()) {
            PhoneBookManager.preparePhoneBookContacts(getContext());
        }
    }

Dieser PhoneBookManager.getPhoneBookContacts liest Kontakte aus dem Telefonbuch und füllt sie in die Hashmaps. Das ist direkt verwendbar für Listenadapter, um Listen zu zeichnen.

Auf meinem Bildschirm ist eine Schaltfläche. Das öffnet eine Aktivität, in der diese Telefonnummern aufgelistet sind. Wenn ich Adapter direkt über die Liste setze, bevor der vorherige Thread seine Arbeit beendet, passiert dies bei schneller Navigation seltener. Es erscheint die Ausnahme. Das ist der Titel dieser SO-Frage. Also muss ich so etwas in der zweiten Aktivität machen.

Mein Loader in der zweiten Aktivität wartet auf den Abschluss des ersten Threads. Bis es einen Fortschrittsbalken anzeigt. Überprüfen Sie das loadInBackground beider Loader.

Dann erstellt es den Adapter und liefert ihn an die Aktivität, wo ich im ui-Thread setAdapter aufrufe.

Das hat mein Problem gelöst.

Dieser Code ist nur ein Ausschnitt. Sie müssen es ändern, damit es für Sie gut kompiliert wird.

@Override
public Loader<PhoneBookContactAdapter> onCreateLoader(int arg0, Bundle arg1) {
    return new PhoneBookContactLoader(this);
}

@Override
public void onLoadFinished(Loader<PhoneBookContactAdapter> arg0, PhoneBookContactAdapter arg1) {
    contactList.setAdapter(adapter = arg1);
}

/*
 * AsyncLoader to load phonebook and notify the list once done.
 */
private static class PhoneBookContactLoader extends AsyncTaskLoader<PhoneBookContactAdapter> {

    private PhoneBookContactAdapter adapter;

    public PhoneBookContactLoader(Context context) {
        super(context);
    }

    @Override
    public PhoneBookContactAdapter loadInBackground() {
        synchronized (SingleTonProvider.getInstance()) {
            return adapter = new PhoneBookContactAdapter(getContext());    
        }
    }

}

Hoffe das hilft

  • Entschuldigung, wie hast du das gemacht? Wie haben Sie den Hintergrund-Thread-Code und den Notifydatasetchange-Aufruf synchronisiert? Ich verwende doInBackground beim Filtern von Daten in einer AutocompleteTextView und stehe vor demselben Problem … Können Sie mir etwas zur Lösung raten?

    – Tonix

    3. Mai 2014 um 20:32 Uhr

  • @ user3019105 – Zwei Hintergrundthreads synchronisieren. eine, die die Daten im Hintergrund aktualisiert, und eine andere, die den ui-Thread benachrichtigt, setAdadpter zu setzen oder datasetchanged mit den verfügbaren handler.post-Methoden oder runOnUiThread-Methoden zu benachrichtigen. Für meinen speziellen Fall habe ich zwei Loader synchronisiert, wobei ihr doInBackground auf einem Singleton-Objekt synchronisiert ist. Man beginnt mit der Vorbereitung von Daten auf dem Startbildschirm selbst, um schnell für den Dashboard-Bildschirm verfügbar zu sein. Das war mein Bedürfnis.

    – Rohit Sharma

    4. Mai 2014 um 4:28 Uhr


  • Könnten Sie bitte einen Codeausschnitt dieser Implementierung posten? In meinem Fall habe ich das Aufrufen von clear() auf dem Adapter, das Erstellen einer temporären ArrayList von gefilterten Objekten, das Ausführen eines Aufrufs von addAll(tmpArrayList) und schließlich von notificationDataSetChanged() gelöst. Ich mache all diese Aufrufe innerhalb der Methode publishResult() Filter, die im Haupt-Thread läuft. Halten Sie das für eine zuverlässige Lösung?

    – Tonix

    4. Mai 2014 um 8:36 Uhr

  • @ user3019105 überprüfen Sie die bearbeitete Antwort. Hoffe es hilft dir jetzt weiter

    – Rohit Sharma

    4. Mai 2014 um 12:15 Uhr

  • Diese Antwort erfordert viel Lektüre darüber, warum es funktioniert. tutorials.jenkov.com/java-concurrency/synchronized.html ist ein guter Anfang

    – abdu

    11. September 2014 um 8:22 Uhr

Ich habe das gelöst, indem ich 2 Listen habe. Eine Liste verwende ich nur für den Adapter, und ich mache alle Datenänderungen/-aktualisierungen auf der anderen Liste. Dadurch kann ich Aktualisierungen an einer Liste in einem Hintergrund-Thread vornehmen und dann die “Adapter”-Liste im Haupt-/UI-Thread aktualisieren:

List<> data = new ArrayList<>();
List<> adapterData = new ArrayList();

...
adapter = new Adapter(adapterData);
listView.setAdapter(adapter);

// Whenever data needs to be updated, it can be done in a separate thread
void updateDataAsync()
{
    new Thread(new Runnable()
    {
        @Override
        public void run()
        {
            // Make updates the "data" list.
            ...

            // Update your adapter.
            refreshList();
        }
    }).start();
}

void refreshList()
{
    runOnUiThread(new Runnable()
    {
        @Override
        public void run()
        {
            adapterData.clear();
            adapterData.addAll(data);
            adapter.notifyDataSetChanged();
            listView.invalidateViews();
        }
    });
}

Ich habe diesen Code geschrieben und etwa 12 Stunden lang in einem 2.1-Emulator-Image ausgeführt und habe keine IllegalStateException erhalten. Ich werde dem Android-Framework in diesem Fall den Vorteil des Zweifels geben und sagen, dass es höchstwahrscheinlich ein Fehler in Ihrem Code ist. Ich hoffe das hilft. Vielleicht können Sie es an Ihre Liste und Daten anpassen.

public class ListViewStressTest extends ListActivity {
    ArrayAdapter<String> adapter;
    ListView list;
    AsyncTask<Void, String, Void> task;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        this.adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
        this.list = this.getListView();

        this.list.setAdapter(this.adapter);

        this.task = new AsyncTask<Void, String, Void>() {
            Random r = new Random();
            int[] delete;
            volatile boolean scroll = false;

            @Override
            protected void onProgressUpdate(String... values) {
                if(scroll) {
                    scroll = false;
                    doScroll();
                    return;
                }

                if(values == null) {
                    doDelete();
                    return;
                }

                doUpdate(values);

                if(ListViewStressTest.this.adapter.getCount() > 5000) {
                    ListViewStressTest.this.adapter.clear();
                }
            }

            private void doScroll() {
                if(ListViewStressTest.this.adapter.getCount() == 0) {
                    return;
                }

                int n = r.nextInt(ListViewStressTest.this.adapter.getCount());
                ListViewStressTest.this.list.setSelection(n);
            }

            private void doDelete() {
                int[] d;
                synchronized(this) {
                    d = this.delete;
                }
                if(d == null) {
                    return;
                }
                for(int i = 0 ; i < d.length ; i++) {
                    int index = d[i];
                    if(index >= 0 && index < ListViewStressTest.this.adapter.getCount()) {
                        ListViewStressTest.this.adapter.remove(ListViewStressTest.this.adapter.getItem(index));
                    }
                }
            }

            private void doUpdate(String... values) {
                for(int i = 0 ; i < values.length ; i++) {
                    ListViewStressTest.this.adapter.add(values[i]);
                }
            }

            private void updateList() {
                int number = r.nextInt(30) + 1;
                String[] strings = new String[number];

                for(int i = 0 ; i < number ; i++) {
                    strings[i] = Long.toString(r.nextLong());
                }

                this.publishProgress(strings);
            }

            private void deleteFromList() {
                int number = r.nextInt(20) + 1;
                int[] toDelete = new int[number];

                for(int i = 0 ; i < number ; i++) {
                    int num = ListViewStressTest.this.adapter.getCount();
                    if(num < 2) {
                        break;
                    }
                    toDelete[i] = r.nextInt(num);
                }

                synchronized(this) {
                    this.delete = toDelete;
                }

                this.publishProgress(null);
            }

            private void scrollSomewhere() {
                this.scroll = true;
                this.publishProgress(null);
            }

            @Override
            protected Void doInBackground(Void... params) {
                while(true) {
                    int what = r.nextInt(3);

                    switch(what) {
                        case 0:
                            updateList();
                            break;
                        case 1:
                            deleteFromList();
                            break;
                        case 2:
                            scrollSomewhere();
                            break;
                    }

                    try {
                        Thread.sleep(0);
                    } catch(InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

        };

        this.task.execute(null);
    }
}

  • Danke für deine Arbeit! Mir ist aufgefallen, dass für die ArrayAdapter-Methode hasStableIds()=false; Meine Implementierung gab true zurück, was nicht ganz richtig war, da die Zeilen vor jeder NotifyDataSetChanged () sortiert wurden. Ich werde es versuchen – wenn die Abstürze bestehen bleiben, werde ich die Untersuchung fortsetzen. Mit freundlichen Grüßen!

    – tomasch

    29. Juni 2010 um 23:15 Uhr

Android ListView IllegalStateException Der Inhalt des Adapters hat sich geandert
HJWAJ

Vor einigen Tagen traf ich auf das gleiche Problem und verursachte mehrere tausend Abstürze pro Tag, etwa 0,1% der Benutzer treffen auf diese Situation. Ich habe es versucht setVisibility(GONE/VISIBLE) und requestLayout()aber die Anzahl der Abstürze nimmt nur geringfügig ab.

Und ich habe es endlich gelöst. Nichts mit setVisibility(GONE/VISIBLE). Nichts mit requestLayout().

Schließlich fand ich den Grund dafür, dass ich a verwendet habe Handler anrufen notifyDataSetChanged() nach Update-Daten, was zu einer Art von führen kann:

  1. Aktualisiert Daten in einem Modellobjekt (ich nenne es eine DataSource)
  2. Der Benutzer berührt die Listenansicht (die möglicherweise aufgerufen wird checkForTap()/onTouchEvent() und ruft schließlich an layoutChildren() )
  3. Adapter erhält Daten vom Modellobjekt und Aufruf notifyDataSetChanged() und Ansichten aktualisieren

Und ich habe noch einen weiteren Fehler gemacht getCount(), getItem() und getView(), verwende ich direkt Felder in DataSource, anstatt sie in den Adapter zu kopieren. Also stürzt es schließlich ab, wenn:

  1. Der Adapter aktualisiert die Daten, die die letzte Antwort gibt
  2. Bei der nächsten Antwort aktualisiert DataSource die Daten, was zu einer Änderung der Elementanzahl führt
  3. Der Benutzer berührt die Listenansicht, was ein Antippen, eine Bewegung oder ein Umblättern sein kann
  4. getCount() und getView() aufgerufen wird, und listview feststellt, dass Daten nicht konsistent sind, und Ausnahmen wie auslöst java.lang.IllegalStateException: The content of the adapter has changed but.... Eine weitere häufige Ausnahme ist eine IndexOutOfBoundException wenn Sie Kopf-/Fußzeile verwenden ListView.

Die Lösung ist also einfach. Ich kopiere einfach Daten von meiner Datenquelle in den Adapter, wenn mein Handler den Adapter auslöst, um Daten und Aufrufe abzurufen notifyDataSetChanged(). Der Absturz passiert jetzt nie wieder.

  • Danke für deine Arbeit! Mir ist aufgefallen, dass für die ArrayAdapter-Methode hasStableIds()=false; Meine Implementierung gab true zurück, was nicht ganz richtig war, da die Zeilen vor jeder NotifyDataSetChanged () sortiert wurden. Ich werde es versuchen – wenn die Abstürze bestehen bleiben, werde ich die Untersuchung fortsetzen. Mit freundlichen Grüßen!

    – tomasch

    29. Juni 2010 um 23:15 Uhr

1646319796 14 Android ListView IllegalStateException Der Inhalt des Adapters hat sich geandert
Aaronvargas

Wäre dies zeitweise passiert, stellte sich heraus, dass ich dieses Problem nur hatte, wenn die Liste gescrollt wurde, nachdem auf das letzte Element „Mehr laden“ geklickt wurde. Wenn die Liste nicht gescrollt wurde, funktionierte alles einwandfrei.

Nach VIEL Debugging war es ein Fehler meinerseits, aber auch eine Inkonsistenz im Android-Code.

Wenn die Validierung erfolgt, wird dieser Code in ListView ausgeführt

        } else if (mItemCount != mAdapter.getCount()) {
            throw new IllegalStateException("The content of the adapter has changed but "
                    + "ListView did not receive a notification. Make sure the content of "

Aber wenn onChange passiert, wird dieser Code in AdapterView (übergeordnet von ListView) ausgelöst.

    @Override
    public void onChanged() {
        mDataChanged = true;
        mOldItemCount = mItemCount;
        mItemCount = getAdapter().getCount();

Beachten Sie, dass der Adapter NICHT garantiert gleich ist!

Da es sich in meinem Fall um einen ‘LoadMoreAdapter’ handelte, habe ich den WrappedAdapter im getAdapter-Aufruf zurückgegeben (für den Zugriff auf die zugrunde liegenden Objekte). Dies führte dazu, dass die Zählungen aufgrund des zusätzlichen Elements „Load More“ und der ausgelösten Ausnahme unterschiedlich waren.

Ich habe dies nur getan, weil die Dokumentation es so erscheinen lässt, als wäre es in Ordnung

ListView.getAdapter javadoc

Gibt den derzeit in dieser ListView verwendeten Adapter zurück. Der zurückgegebene Adapter ist möglicherweise nicht derselbe Adapter, der an setAdapter(ListAdapter) übergeben wird, sondern ein WrapperListAdapter.

  • Ich habe ein ähnliches Problem. Können Sie bitte vorschlagen, wie man es repariert.

    – 13. Mai Dank

    14. Mai 2015 um 8:59 Uhr

  • Wenn Sie sich die beiden obigen Codebeispiele ansehen, sehen Sie mAdapter in ListView und getAdapter() in einem übergeordneten Element von ListView (AdapterView). Wenn Sie getAdapter() überschreiben, ist das Ergebnis von getAdapter() möglicherweise nicht der mAdapter. Wenn die Anzahl der 2 Adapter unterschiedlich ist, erhalten Sie den Fehler.

    – Aaronvargas

    15. Mai 2015 um 21:57 Uhr

924580cookie-checkAndroid, ListView IllegalStateException: “Der Inhalt des Adapters hat sich geändert, aber ListView hat keine Benachrichtigung erhalten”

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy