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

188

Was ich tun möchte : Führen Sie einen Hintergrund-Thread aus, der den ListView-Inhalt berechnet und ListView teilweise aktualisiert, während die Ergebnisse berechnet werden.

Was ich weiß, muss ich vermeiden : Ich kann mich nicht mit ListAdapter-Inhalten aus dem Hintergrund-Thread anlegen, daher habe ich AsyncTask geerbt und das Ergebnis (Einträge zum Adapter hinzufügen) von onProgressUpdate veröffentlicht. Mein Adapter verwendet ArrayList von Ergebnisobjekten. Alle Vorgänge auf diesen Arraylisten werden synchronisiert.

Forschung anderer Leute : 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 list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)Block in onProgressUpdate hinzufügte , verringerten sich die Abstürze um den Faktor 10, verschwanden aber nicht. (es wurde als Antwort vorgeschlagen )

Was ich manchmal bekam : Bitte beachten Sie, dass es sehr selten vorkommt (einmal pro Woche für einen von 3.5k Benutzern). Aber ich möchte diesen Fehler vollständig beseitigen. Hier ist eine teilweise Stapelverfolgung:

`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, habe ich notifyDataSetChangedalle 5 Einfügungen aufgerufen, um ein Flackern und plötzliche Listenänderungen zu vermeiden. Dies ist nicht möglich. Benachrichtigen Sie den Adapter immer, wenn sich die Basisliste ändert. Dieser Fehler ist für mich jetzt schon lange vorbei.

Tomash
quelle
3
Haben Sie notifyDataSetChanged () aufgerufen?
Denis Palnitsky
1
Natürlich gibt es in onProgressUpdate die folgende Reihenfolge: list.setVisibility (GONE) - addObjectToList / synchronisierte Operation auf list / - notifyDataSetChanged () - list.setVisibility (VISIBLE) (und ohne Sichtbarkeitsänderungen tritt eine Ausnahme weitaus häufiger auf)
tomash
1
Ändern Sie die zugrunde liegende ArrayList in einem anderen Thread? Alle Änderungen, auch an der ArrayList, auf die der Adapter verweist, müssen im UI-Thread vorgenommen werden.
Rich Schuler
2
@Qberticus - wie ich klar gesagt habe, ändere ich ArrayList nicht von einem anderen Thread, sondern von der Methode onProgressUpdate von AcyncTask - es funktioniert im GUI-Thread.
Tomash
1
@ Tomash Ich habe die gleiche Ausnahme Ich benutze Pull zum Aktualisieren und in bei der Ausführung nach der Ausführung habe ich adapter.notifyDataSetChanged () geschrieben; lvAutherlist.completeRefreshing (); aber manchmal bekam dieser Fehler, wie man es löst
Khan

Antworten:

119

Ich hatte das gleiche Problem.

Ich habe Elemente ArrayListaußerhalb des UI-Threads hinzugefügt .

Lösung: Ich habe beides getan adding the itemsund notifyDataSetChanged()den UI-Thread aufgerufen .

Mullins
quelle
4
Ich habe beide Elemente hinzugefügt und notifyDataSetChanged () im UI-Thread aufgerufen und dies behoben.
Mullins
23
Genialer Kommentar, wirklich.
Dentex
2
Dies ist ein Multithreading-Problem und die Verwendung ordnungsgemäß synchronisierter Blöcke. Dies kann verhindert werden. Ohne zusätzliche Dinge auf den UI-Thread zu setzen und die Reaktionsfähigkeit der App zu beeinträchtigen. Überprüfen Sie meine Antwort unten.
Javanator
2
Für zukünftige Leser ist es eine gute Sache, Berechnungen im Hintergrundthread durchzuführen. Sie sollten jedoch die Ergebnisse an den UI-Thread übergeben und die Elemente zur Adapterbasissammlung hinzufügen und den Adapter im selben Codeblock benachrichtigen.
Tomash
4
@ Mullins Können Sie bitte ein Beispiel für Ihre Lösung zeigen? Ich stecke bei der gleichen Ausgabe fest
Jas
27

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

requestLayout();

aus der Klasse ListView

gian1200
quelle
35
Wann sollen wir diese Methode aufrufen?
JehandadK
3
@DeBuGGeR, nachdem Sie entweder Elemente zur ListView hinzugefügt oder den Adapter geändert haben.
Ahmet Noyan Kızıltan
Was ist, wenn ich Elemente in Asynctask hinzufüge
Dr. aNdRO
2
Nur zur Veranschaulichung, @ 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 Sie benötigen , um Update , wie Sie gehen, verwenden AsyncTask.publishProgress () und AsyncTask.onProgressUpdate () , die ebenfalls in dem UI - Thread ausgeführt wird .
Nicole Borrelli
21

Dies ist ein MultiThreading- Problem und die Verwendung ordnungsgemäß synchronisierter Blöcke. Dies kann verhindert werden. Ohne zusätzliche Dinge auf den UI-Thread zu setzen 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 Ändern der Adapterdaten von UI Thread behoben werden. Das wird funktionieren, ist aber eine schnelle und einfache Lösung, aber nicht die beste.

Wie Sie für einen Normalfall sehen können. Das Aktualisieren des Datenadapters vom Hintergrundthread 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 notifydatasetchange aufruft. Dieses Problem sollte weg sein. Da für mich weg und ich aktualisiere immer noch die Daten aus dem Hintergrund-Thread.

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

Mein Loader auf dem Hauptbildschirm lädt die Telefonbuchkontakte im Hintergrund 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 aus. Dies ist direkt für Listenadapter zum Zeichnen von Listen verwendbar.

Auf meinem Bildschirm befindet sich eine Schaltfläche. Das öffnet eine Aktivität, in der diese Telefonnummern aufgelistet sind. Wenn ichAdapter direkt über die Liste setze, bevor der vorherige Thread seine Arbeit beendet, kommt es schnell vor, dass die Navigation seltener vorkommt. Es wird die Ausnahme angezeigt. Dies 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 den loadInBackground beider Lader.

Dann erstellt es den Adapter und liefert ihn an die Aktivität, bei der ich auf dem UI-Thread setAdapter aufrufe.

Das hat mein Problem gelöst.

Dieser Code ist nur ein Ausschnitt. Sie müssen es ändern, um gut für Sie zu kompilieren.

@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

Javanator
quelle
Entschuldigung, wie hast du das gemacht? Wie haben Sie den Hintergrund-Thread-Code und den Aufruf von notifydatasetchange synchronisiert? Ich verwende ein doInBackground, wenn ich Daten in einer AutocompleteTextView filtere, und ich habe das gleiche Problem ... Können Sie mir etwas zur Lösung empfehlen?
Tonix
@ user3019105 - Synchronisieren von zwei Hintergrund-Threads. Eine, die die Daten im Hintergrund aktualisiert, und eine, die den UI-Thread an setAdadpter oder notifydatasetchanged mit den verfügbaren handler.post-Methoden oder runOnUiThread-Methoden benachrichtigt. Für meinen speziellen Fall habe ich zwei Loader mit ihrem doInBackground synchronisiert, der auf einem Singleton-Objekt synchronisiert ist. Beginnen Sie mit der Vorbereitung der Daten auf dem Begrüßungsbildschirm selbst, damit sie schnell für den Dashboard-Bildschirm verfügbar sind. Das war mein Bedürfnis.
Javanator
Könnten Sie bitte einen Codeausschnitt dieser Implementierung posten? In meinem Fall habe ich den Aufruf von clear () für den Adapter behoben, eine temporäre ArrayList <T> mit gefilterten Objekten erstellt und dann addAll (tmpArrayList) und schließlich notifyDataSetChanged () aufgerufen. Ich mache all diese Aufrufe innerhalb der PublishResult () Filter-Methode, die auf dem Haupt-Thread ausgeführt wird. Halten Sie dies für eine zuverlässige Lösung?
Tonix
@ user3019105 Überprüfen Sie die bearbeitete Antwort. Hoffe, es ist jetzt hilfreich für Sie
Javanator
Diese Antwort erfordert viel Lesen darüber, warum es funktioniert. tutorials.jenkov.com/java-concurrency/synchronized.html ist ein guter Anfang
abdu
15

Ich habe das gelöst, indem ich 2 Listen habe. Eine Liste verwende ich nur für den Adapter und führe alle Datenänderungen / -aktualisierungen in der anderen Liste durch. Auf diese Weise kann ich Aktualisierungen für eine Liste in einem Hintergrundthread durchführen und anschließend 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();
        }
    });
}
Triade
quelle
7

Ich habe diesen Code geschrieben und ihn ~ 12 Stunden lang in einem 2.1-Emulator-Image laufen lassen und die IllegalStateException nicht erhalten. Ich werde dem Android-Framework den Vorteil des Zweifels an diesem 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);
    }
}
Rich Schuler
quelle
Danke für deine Arbeit! Ich habe festgestellt, dass für die ArrayAdapter-Methode hasStableIds () = false; Meine Implementierung gab true zurück, was nicht ganz richtig war, da die Zeilen vor jedem notifyDataSetChanged () sortiert wurden. Ich werde es versuchen - wenn Abstürze überleben, werde ich die Untersuchung fortsetzen. Freundliche Grüße!
Tomash
4

Vor einigen Tagen bin ich auf dasselbe Problem gestoßen und verursache mehrere tausend Abstürze pro Tag. Etwa 0,1% der Benutzer treffen 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, warum ich a verwendet habe Handler, um notifyDataSetChanged()nach der Aktualisierung der Daten aufzurufen , was zu einer Art führen kann:

  1. Aktualisiert Daten zu einem Modellobjekt (ich nenne es eine DataSource)
  2. Benutzer berührt Listenansicht (die anrufen kann checkForTap()/ onTouchEvent()und schließlich anruft layoutChildren())
  3. Der Adapter ruft Daten vom Modellobjekt ab und ruft notifyDataSetChanged()Ansichten auf und aktualisiert sie

Und ich machte einen weiteren Fehler , dass in getCount(), getItem()und getView()ich verwenden , um Felder in Datasource direkt, anstatt Kopie sie an den Adapter. Also stürzt es schließlich ab, wenn:

  1. Der Adapter aktualisiert die Daten, die die letzte Antwort liefert
  2. Bei der nächsten Antwort aktualisiert DataSource die Daten, wodurch sich die Anzahl der Elemente ändert
  3. Der Benutzer berührt die Listenansicht, bei der es sich um ein Tippen, Bewegen oder Drehen handeln kann
  4. getCount()und getView()wird aufgerufen, und listview stellt fest, dass Daten nicht konsistent sind, und löst Ausnahmen wie aus java.lang.IllegalStateException: The content of the adapter has changed but.... Eine weitere häufige Ausnahme ist die IndexOutOfBoundExceptionVerwendung von Kopf- / Fußzeilen in ListView.

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

HJWAJ
quelle
1
Das war mein Problem. In meinem Fall gab getCount () den neuen verfügbaren Zähler zurück, bevor notifyDataSetChanged aufgerufen wurde. Da meine Liste 'virtuell' war, erfasse ich jetzt die aktualisierte Anzahl in meiner notifyDataSetChanged-Methode und gebe diese zwischengespeicherte Anzahl zurück, wenn ich über getCount () nach der Anzahl gefragt werde.
Glenn
3

Wenn dies zeitweise passiert, stellte sich heraus, dass ich dieses Problem nur hatte, als die Liste gescrollt wurde, nachdem auf das letzte Element "Mehr laden" geklickt wurde. Wenn die Liste nicht gescrollt wurde, funktionierte alles einwandfrei.

Nach VIELEM Debugging war es ein Fehler von meiner Seite, 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 "

Wenn onChange auftritt, wird dieser Code in AdapterView (übergeordnetes Element von ListView) ausgelöst.

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

Beachten Sie, dass der Adapter NICHT garantiert derselbe ist!

In meinem Fall habe ich, da es sich um einen 'LoadMoreAdapter' handelte, den WrappedAdapter im getAdapter-Aufruf zurückgegeben (für den Zugriff auf die zugrunde liegenden Objekte). Dies führte dazu, dass die Anzahl aufgrund des zusätzlichen Elements "Mehr laden" und der ausgelösten Ausnahme unterschiedlich war.

Ich habe das nur gemacht, weil die Dokumente den Anschein erwecken, dass es in Ordnung ist

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 wurde, sondern möglicherweise ein WrapperListAdapter.

aaronvargas
quelle
Ich habe ein ähnliches Problem. Können Sie bitte vorschlagen, wie Sie das Problem beheben können?
13.
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, wird der Fehler angezeigt.
Aaronvargas
3

Mein Problem betraf die Verwendung eines Filters zusammen mit der ListView.

Beim Festlegen oder Aktualisieren des zugrunde liegenden Datenmodells von ListView habe ich Folgendes getan:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    getFilter().filter(filter);
}

Das Aufrufen filter()der letzten Zeile führt (und muss) dazu notifyDataSetChanged(), dass die Filtermethode aufgerufen publishResults()wird. Dies kann manchmal in Ordnung sein, insbesondere in meinem schnellen Nexus 5. In Wirklichkeit verbirgt es jedoch einen Fehler, den Sie bei langsameren Geräten oder unter ressourcenintensiven Bedingungen bemerken werden.

Das Problem besteht darin, dass die Filterung asynchron erfolgt und somit zwischen dem Ende der filter()Anweisung und dem Aufruf von publishResults()beiden anderen UI-Thread-Codes im UI-Thread möglicherweise den Inhalt des Adapters ausgeführt und geändert wird.

Die eigentliche Korrektur ist einfach. Rufen Sie einfach an, notifyDataSetChanged()bevor Sie die Filterung anfordern:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    notifyDataSetChanged(); // Fix
    getFilter().filter(filter);
}
cprcrack
quelle
3

Ich habe eine Liste, wenn Feed-Objekte. Es wird an einen Thread ohne Benutzeroberfläche angehängt und abgeschnitten. Es funktioniert gut mit Adapter unten. Ich rufe FeedAdapter.notifyDataSetChangedtrotzdem UI-Thread auf, aber etwas später. Ich mag das, weil meine Feed-Objekte im lokalen Dienst im Speicher bleiben, auch wenn die Benutzeroberfläche tot ist.

public class FeedAdapter extends BaseAdapter {
    private int size = 0;
    private final List<Feed> objects;

    public FeedAdapter(Activity context, List<Feed> objects) {
        this.context = context;
        this.objects = objects;
        size = objects.size();
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        ...
    }

    @Override
    public void notifyDataSetChanged() {
        size = objects.size();

        super.notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return size;
    }

    @Override
    public Object getItem(int position) {
        try {
            return objects.get(position);
        } catch (Error e) {
            return Feed.emptyFeed;
        }
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
}
ilya
quelle
2

Ich hatte das gleiche Problem mit genau dem gleichen Fehlerprotokoll. In meinem Fall onProgress()fügt AsyncTask die Werte mit dem Adapter hinzu mAdapter.add(newEntry). Um zu vermeiden, dass die Benutzeroberfläche weniger reagiert, setze ich 4-mal pro Sekunde mAdapter.setNotifyOnChange(false)und rufe an mAdapter.notifyDataSetChanged(). Einmal pro Sekunde wird das Array sortiert.

Dies funktioniert gut und macht sehr süchtig, aber leider ist es möglich, es durch häufiges Berühren der angezeigten Listenelemente zum Absturz zu bringen.

Aber anscheinend habe ich eine akzeptable Problemumgehung gefunden. Ich vermute, dass der Adapter, selbst wenn Sie nur am UI-Thread arbeiten, nicht viele Änderungen an seinen Daten akzeptiert, ohne ihn aufzurufen notifyDataSetChanged(). Aus diesem Grund habe ich eine Warteschlange erstellt, in der alle neuen Elemente gespeichert werden, bis die genannten 300 ms abgelaufen sind. Wenn dieser Moment erreicht ist, füge ich alle gespeicherten Gegenstände auf einmal hinzu und rufe an notifyDataSetChanged(). Bis jetzt konnte ich die Liste nicht mehr zum Absturz bringen .

Lars K.
quelle
2

Selbst wenn ich in meiner XMPP-Benachrichtigungsanwendung auf dasselbe Problem gestoßen bin, muss die Empfängernachricht wieder zur Listenansicht hinzugefügt werden (implementiert mit ArrayList). Als ich versuchte, den Empfängerinhalt über MessageListener(separater Thread) hinzuzufügen , wurde die Anwendung mit dem obigen Fehler beendet. Ich habe dieses Problem gelöst, indem ich den Inhalt zu meiner arraylist& setListviewadapaterthrough- runOnUiThreadMethode hinzugefügt habe, die Teil der Aktivitätsklasse ist. Dies löste mein Problem.

Balaji
quelle
1

Ich hatte ein ähnliches Problem. So habe ich es in meinem Fall gelöst. Ich überprüfe, ob dies taskbereits der Fall ist RUNNINGoder ob FINISHEDeine Aufgabe nur einmal ausgeführt werden kann. Unten sehen Sie einen teilweisen und angepassten Code aus meiner Lösung.

public class MyActivity... {
    private MyTask task;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       // your code
       task = new MyTask();
       setList();
    }

    private void setList() {
    if (task != null)
        if (task.getStatus().equals(AsyncTask.Status.RUNNING)){
            task.cancel(true);
            task = new MyTask();
            task.execute();         
        } else if (task.getStatus().equals(AsyncTask.Status.FINISHED)) {
            task = new MyTask();
            task.execute();
        } else 
            task.execute();
    }

    class MyTask extends AsyncTask<Void, Item, Void>{
       List<Item> Itens;

       @Override
       protected void onPreExecute() {

        //your code

        list.setVisibility(View.GONE);
        adapterItem= new MyListAdapter(MyActivity.this, R.layout.item, new ArrayList<Item>());
        list.setAdapter(adapterItem);

        adapterItem.notifyDataSetChanged();
    }

    @Override
    protected Void doInBackground(Void... params) {

        Itens = getItens();
        for (Item item : Itens) {
            publishProgress(item );
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(Item ... item ) {           
        adapterItem.add(item[0]);
    }

    @Override
    protected void onPostExecute(Void result) {
        //your code
        adapterItem.notifyDataSetChanged();     
        list.setVisibility(View.VISIBLE);
    }

}

}
Leonardo Costa
quelle
1

Ich hatte das gleiche Problem und habe es gelöst. Mein Problem war, dass ich einen listviewmit einem Array-Adapter und mit Filter verwendete. Bei der Methode habe performFilteringich mit dem Array herumgespielt, das die Daten enthält, und es war das Problem, da diese Methode nicht auf dem UI-Thread ausgeführt wird und EVENTUELL einige Probleme aufwirft.

Gusthema
quelle
1

Eine Ursache für diesen Absturz ist, dass sich das ArrayListObjekt nicht vollständig ändern kann. Wenn ich also einen Artikel entferne, muss ich Folgendes tun:

mList.clear();
mList.addAll(newDataList);

Dies hat den Absturz für mich behoben.

andude
quelle
1

In meinem Fall habe ich die Methode GetFilter()auf einem Adapter aus der TextWatcher()Methode in der Hauptaktivität aufgerufen und die Daten mit einer For-Schleife hinzugefügt GetFilter(). Die Lösung bestand darin, die For-Schleife in der AfterTextChanged()Hauptaktivität in die Submethode zu ändern und den Aufruf von zu löschenGetFilter()

sek13300
quelle
1
Bitte klären Sie Ihre Lösung.
Ich bin
1
adapter.notifyDataSetChanged()
Nithin Joseph
quelle
2
Bei Stack Overflow empfiehlt es sich, eine Erklärung hinzuzufügen, warum Ihre Lösung funktionieren sollte oder besser ist als die vorhandenen Lösungen. Weitere Informationen finden Sie unter Beantworten .
Samuel Liew
0

Ich habe auch genau den gleichen Fehler erhalten und AsyncTask verwendet:

`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... etc

Ich habe es gelöst, indem ich adapter.notifyDataSetChanged();am Ende meines UI-Threads meine AsyncTask onPostExecute-Methode eingefügt habe. So was :

 protected void onPostExecute(Void aVoid) {

 all my other stuff etc...
    all my other stuff etc...

           adapter.notifyDataSetChanged();

                }

            });
        }

Jetzt funktioniert meine App.

BEARBEITEN: Tatsächlich stürzte meine App immer noch etwa alle 1 von 10 Mal ab und gab den gleichen Fehler aus.

Irgendwann bin ich runOnUiThreadauf einen früheren Beitrag gestoßen, den ich für nützlich hielt. Also habe ich es wie folgt in meine doInBackground-Methode eingefügt:

@Override
protected Void doInBackground(Void... voids) {

    runOnUiThread(new Runnable() {
                      public void run() { etc... etc...

Und ich habe die adapter.notifyDataSetChanged();Methode entfernt. Jetzt stürzt meine App nie mehr ab.

CHarris
quelle
0

Bitte versuchen Sie eine dieser Lösungen:

  1. Wenn Sie der Datenliste in einem Thread (oder einer doInBackgroundMethode) ein neues Objekt hinzufügen , tritt dieser Fehler manchmal auf. Die Lösung lautet: Erstellen Sie eine temporäre Liste und fügen Sie dieser Liste im Thread (oder doInBackground) Daten hinzu. Kopieren Sie dann alle Daten aus der temporären Liste in die Liste der Adapter im UI-Thread (oder onPostExcute).

  2. Stellen Sie sicher, dass alle UI-Updates im UI-Thread aufgerufen werden.

Phuc Tran
quelle
0

Ich hatte das gleiche Problem beim Hinzufügen neuer Daten in Lazy Image Loader, die ich gerade gestellt habe

         adapter.notifyDataSetChanged();

im

       protected void onPostExecute(Void args) {
        adapter.notifyDataSetChanged();
        // Close the progressdialog
        mProgressDialog.dismiss();
         }

hoffe es hilft dir

Hobii Sgonf
quelle
0

Wie @Mullins sagte "
Ich habe beide die Elemente hinzugefügt und notifyDataSetChanged()den UI-Thread aufgerufen und dies behoben. - Mullins".

In meinem Fall habe ich asynctaskund ich habe notifyDataSetChanged()die doInBackground()Methode aufgerufen und das Problem ist gelöst, als ich von onPostExecute()anrief, erhielt ich die Ausnahme.

Ciro Mine
quelle
0

Ich hatte einen Brauch ListAdapter und rief super.notifyDataSetChanged()am Anfang und nicht am Ende der Methode auf

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}
Pascalius
quelle
0

Ich hatte die gleiche Situation, ich hatte viele Buttongroups, die meinen Artikel in die Listenansicht aufgenommen haben, und ich habe einige boolesche Werte in meinem Artikel geändert, wie z. B. Inhaber.rbVar.setOnclik ...

Mein Problem trat auf, weil ich eine Methode in getView () aufrief. und speicherte ein Objekt in sharepreference, so dass ich oben den gleichen Fehler hatte

Wie ich es gelöst habe; Ich habe meine Methode in getView () entfernt, umDataSetInvalidated () zu benachrichtigen, und das Problem ist behoben

   @Override
    public void notifyDataSetChanged() {
        saveCurrentTalebeOnShare(currentTalebe);
        super.notifyDataSetChanged();
    }
Sam
quelle
0

Ich hatte das gleiche Problem. Endlich habe ich die Lösung

Wenn die Softtastatur vorhanden ist, schließen Sie sie zuerst, bevor Sie die Listenansicht aktualisieren. Danach Datenquelle einstellen und notifydatasetchanged () aufrufen.

Beim internen Schließen der Tastatur aktualisiert listview die Benutzeroberfläche. Es ruft weiter an, bis die Tastatur geschlossen wird. Wenn sich die Datenquelle ändert, wird diese Ausnahme ausgelöst. Wenn Daten in onActivityResult aktualisiert werden, besteht die Möglichkeit eines gleichen Fehlers.

 InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(v.getWindowToken(), 0);

        view.postDelayed(new Runnable() {
            @Override
            public void run() {
                refreshList();
            }
        },100L);
Sreejith
quelle
0

Meine Lösung:

1) Erstellen Sie eine temp ArrayList.

2) erledigen Sie Ihre schweren Arbeiten (SQLite Row Fetch, ...) in doInBackground Führen Methode aus und fügen Sie der temporären Arrayliste Elemente hinzu.

3) Fügen Sie alle Elemente aus der temporären Liste in die Arrayliste Ihrer Listenansicht ein onPostExecute.

note:Möglicherweise möchten Sie einige Elemente aus der Listenansicht und auch aus der SQLite-Datenbank löschen und möglicherweise einige Dateien löschen, die sich auf Elemente von der SD-Karte beziehen. Entfernen Sie einfach Elemente aus der Datenbank, entfernen Sie die zugehörigen Dateien und fügen Sie sie der temporären Arrayliste hinzu background thread. UI threadLöschen Sie dann in temporäre Arrayliste vorhandene Elemente aus der Arrayliste der Listenansicht.

Hoffe das hilft.

Niemand8
quelle