Android, ListView IllegalStateException: „Zawartość adaptera uległa zmianie, ale ListView nie otrzymało powiadomienia”

189

Co chcę zrobić : uruchomić wątek w tle, który oblicza zawartość ListView i częściowo aktualizować ListView, a wyniki są obliczane.

Wiem, czego muszę unikać : nie mogę zadzierać z zawartością ListAdapter z wątku w tle, więc odziedziczyłem AsyncTask i opublikowałem wynik (dodaj wpisy do adaptera) z onProgressUpdate. Mój adapter używa ArrayList obiektów wynikowych, wszystkie operacje na tych tablicach arraylistów są zsynchronizowane.

Badania z innymi ludźmi : jest bardzo cenne dane tutaj . Cierpiałem także na prawie codzienne awarie dla grupy ~ 500 użytkowników, a kiedy dodałem list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)blok w onProgressUpdate, awarie zostały zmniejszone 10-krotnie, ale nie zniknęły. (zasugerowano w odpowiedzi )

Co czasami dostaję : zauważ, że zdarza się to bardzo rzadko (raz w tygodniu dla jednego z 3,5 tys. Użytkowników). Ale chciałbym całkowicie pozbyć się tego błędu. Oto częściowe śledzenie stosu:

`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)
[...]

Wsparcie? Już nie potrzebne, patrz poniżej

OSTATECZNA ODPOWIEDŹ: Jak się okazało, dzwoniłem notifyDataSetChangedco 5 wstawek, aby uniknąć migotania i nagłych zmian listy. Nie można tego zrobić w ten sposób, zawsze powiadamiaj adapter o zmianie listy podstawowej. Ten błąd już dawno dla mnie minął.

tłuc
źródło
3
Czy wywołałeś powiadomienieataDataSetChanged ()?
Denis Palnitsky
1
Oczywiście w onProgressUpdate występuje sekwencja: list.setVisibility (GONE) - addObjectToList / operacja synchroniczna na liście / - powiadomienieDataSetChanged () - list.setVisibility (WIDOCZNE) (i bez modyfikacji widoczności wyjątek zdarza się znacznie częściej)
tomash
1
Czy modyfikujesz bazową ArrayList w innym wątku? Wszystkie zmiany, nawet w ArrayList, do której odwołuje się adapter, muszą nastąpić w wątku interfejsu użytkownika.
Rich Schuler
2
@Qberticus - jak jasno stwierdziłem, nie modyfikuję ArrayList z innego wątku, ale z metody onProgressUpdate AcyncTask - działa w wątku GUI.
tomash
1
@tomash mam ten sam wyjątek, używam pull do odświeżania, a po uruchomieniu wykonałem napisałem adapter.notifyDataSetChanged (); lvAutherlist.completeRefreshing (); ale czasami pojawia się ten błąd, jak go rozwiązać
Khan

Odpowiedzi:

119

Miałem ten sam problem.

Dodawałem elementy do mojego ArrayListpoza wątkiem interfejsu użytkownika.

Rozwiązanie: Zrobiłem oba adding the itemsi zadzwoniłem notifyDataSetChanged()w wątku interfejsu użytkownika.

Mullins
źródło
4
Zarówno dodałem elementy, jak i wywołałem powiadomieniaDataSetChanged () w wątku interfejsu użytkownika i rozwiązałem ten problem.
Mullins,
23
Genialny komentarz, naprawdę.
dentex
2
Jest to problem dotyczący wielowątkowości i używania prawidłowo zsynchronizowanych bloków Można temu zapobiec. Bez umieszczania dodatkowych rzeczy w wątku interfejsu użytkownika i powodowania utraty responsywności aplikacji. Sprawdź moją odpowiedź poniżej.
Javanator
2
Dla przyszłych czytelników - wykonywanie obliczeń w wątku w tle jest dobrą rzeczą, ale należy przekazać wyniki do wątku interfejsu użytkownika i dodawać elementy do kolekcji podstawowej adaptera i powiadamiać adapter w tym samym bloku kodu.
tomash
4
@Mullins Czy możesz pokazać próbkę swojego rozwiązania? Utknąłem przy tym samym problemie
Jas
27

Miałem ten sam problem, ale naprawiłem go metodą

requestLayout();

z klasy ListView

gian1200
źródło
35
kiedy powinniśmy nazwać tę metodę?
JehandadK,
3
@DeBuGGeR po dodaniu elementów do ListView lub zmianie adaptera.
Ahmet Noyan Kızıltan
a co jeśli dodam elementy do asynctask
Dr. aNdRO
2
Dla przypomnienia, @ Dr.aNdRO, zbierasz swoje wyniki w AsyncTask. doInBackground () i zapisz wyniki, aby zaktualizować listę w AsyncTask.onPostExecute (), która będzie działać w wątku interfejsu użytkownika. Jeśli musisz aktualizować w trakcie pracy, użyj AsyncTask.publishProgress () i AsyncTask.onProgressUpdate (), który również działa w wątku interfejsu użytkownika.
Nicole Borrelli,
21

Jest to problem dotyczący wielowątkowości i używania prawidłowo zsynchronizowanych bloków Można temu zapobiec. Bez umieszczania dodatkowych elementów w wątku interfejsu użytkownika i powodowania utraty responsywności aplikacji.

Ja też stanąłem przed tym samym. A ponieważ najbardziej akceptowana odpowiedź sugeruje, że zmiana danych adaptera z interfejsu użytkownika wątku może rozwiązać problem. To zadziała, ale jest to szybkie i łatwe rozwiązanie, ale nie najlepsze.

Jak widać w normalnym przypadku. Aktualizowanie adaptera danych z wątku w tle i wywoływanie zawiadomieniaataDataSetChanged w wątku interfejsu użytkownika działa.

Ten nielegalny stan powstaje, gdy wątek interfejsu użytkownika aktualizuje widok, a inny wątek w tle ponownie zmienia dane. Ten moment powoduje ten problem.

Więc jeśli zsynchronizujesz cały kod zmieniający dane adaptera i wykonujący wywołanie notifydatasetchange. Ten problem powinien zniknąć. Jak dla mnie i wciąż aktualizuję dane z wątku w tle.

Oto mój kod konkretny przypadku, do którego mogą się odwoływać inni.

Mój moduł ładujący na ekranie głównym ładuje kontakty książki telefonicznej do moich źródeł danych w tle.

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

Ten PhoneBookManager.getPhoneBookContacts odczytuje kontakt z książki telefonicznej i wypełnia je w mapach skrótów. Który jest bezpośrednio użyteczny do narysowania listy przez Adaptery list.

Na moim ekranie jest przycisk. To otwiera działanie, w którym wymienione są te numery telefonów. Jeśli bezpośrednio ustawięAdapter na liście przed zakończeniem poprzedniego wątku, to szybka nawigacja zdarza się rzadziej. Wyskakuje wyjątek. Który jest tytułem tego SO pytania. Więc muszę zrobić coś takiego w drugim ćwiczeniu.

Mój moduł ładujący w drugiej aktywności czeka na zakończenie pierwszego wątku. Aż pokaże pasek postępu. Sprawdź loadInBackground obu ładujących.

Następnie tworzy adapter i dostarcza go do działania, w którym w wątku interfejsu użytkownika nazywam setAdapter.

To rozwiązało mój problem.

Ten kod jest tylko fragmentem kodu. Musisz to zmienić, aby dobrze się skompilować.

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

}

Mam nadzieję że to pomoże

Javanator
źródło
Przepraszam, jak to zrobiłeś? Jak zsynchronizowałeś kod wątku Background i wywołanie notifydatasetchange? Korzystam z doInBackground podczas filtrowania danych w AutocompleteTextView i mam ten sam problem ... Czy mógłbyś mi coś doradzić w celu rozwiązania?
tonix
@ user3019105 - Synchronizacja dwóch wątków w tle. jeden, który aktualizuje dane w tle, a drugi, który powiadamia wątek interfejsu użytkownika o ustawieniu adAdptera lub notifydatasetchanged przy użyciu metod handler.post lub dostępnych metod runOnUiThread. W moim szczególnym przypadku zsynchronizowałem dwa moduły ładujące z ich doInBackground zsynchronizowanym na obiekcie singleton. Rozpocznij przygotowywanie danych na samym ekranie powitalnym, aby były szybko dostępne dla ekranu deski rozdzielczej. To była moja potrzeba.
Javanator
Czy mógłbyś zamieścić fragment kodu tej implementacji? W moim przypadku rozwiązałem wywoływanie clear () na adapterze, tworząc tymczasową ArrayList <T> przefiltrowanych obiektów, a następnie wykonując wywołanie addAll (tmpArrayList) i wreszcie powiadomienieDataSetChanged (). Wszystkie te wywołania wykonuję wewnątrz metody filter PublResult (), działającej w głównym wątku. Czy uważasz, że to niezawodne rozwiązanie?
tonix
@ user3019105 sprawdź poprawioną odpowiedź. Mam nadzieję, że
okaże się pomocny
ta odpowiedź wymaga dużo czytania, dlaczego to działa, tutorials.jenkov.com/java-concurrency/synchronized.html jest dobrym miejscem do rozpoczęcia
abdu
15

Rozwiązałem to przez posiadanie 2 list. Z jednej listy korzystam tylko dla adaptera, a wszystkie zmiany / aktualizacje danych wykonuję na drugiej. To pozwala mi robić aktualizacje na jednej liście w wątku w tle, a następnie aktualizować listę „adaptera” w wątku głównym / UI:

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();
        }
    });
}
triada
źródło
7

Napisałem ten kod i kazałem go uruchamiać w obrazie emulatora 2.1 przez ~ 12 godzin i nie dostałem wyjątku IllegalStateException. Mam zamiar dać frameworkowi Androida wątpliwości co do tego i powiem, że najprawdopodobniej jest to błąd w twoim kodzie. Mam nadzieję, że to pomoże. Może dostosujesz go do swojej listy i danych.

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
źródło
Dziękujemy za Twoją pracę! Zauważyłem, że dla metody ArrayAdapter hasStableIds () = false; moja implementacja zwróciła wartość true, co nie było całkiem właściwe, ponieważ wiersze były sortowane przed każdym powiadomieniemDataSetChanged (). Spróbuję - jeśli wypadki się utrzymają, będę kontynuować dochodzenie. Z poważaniem!
tomash
4

Kilka dni temu napotkałem ten sam problem i powoduje kilka tysięcy awarii dziennie, około 0,1% użytkowników spełnia tę sytuację. Próbowałem setVisibility(GONE/VISIBLE)i requestLayout(), ale liczba awarii tylko nieznacznie spada.

I w końcu to rozwiązałem. Nic z tym setVisibility(GONE/VISIBLE). Nic z tym requestLayout().

W końcu znalazłem powód, dla którego użyłem Handlerwywołania notifyDataSetChanged()po aktualizacji danych, co może prowadzić do:

  1. Aktualizuje dane do obiektu modelu (nazywam to DataSource)
  2. Użytkownik dotyka widoku listy (który może zadzwonić checkForTap()/ onTouchEvent()i ostatecznie wywołać layoutChildren())
  3. Adapter pobiera dane z obiektu modelu notifyDataSetChanged()oraz widoków połączeń i aktualizacji

I popełniłem kolejny błąd getCount(), getItem()i w , i getView()bezpośrednio używam pól w DataSource, zamiast kopiować je do adaptera. Więc w końcu ulega awarii, gdy:

  1. Adapter aktualizuje dane, które daje ostatnia odpowiedź
  2. Przy następnej odpowiedzi DataSource aktualizuje dane, co powoduje zmianę liczby elementów
  3. Użytkownik dotyka widoku listy, który może być dotknięciem, ruchem lub przerzuceniem
  4. getCount()i getView()jest wywoływany, a widok listy znajduje dane niespójne i zgłasza wyjątki, takie jak java.lang.IllegalStateException: The content of the adapter has changed but.... Innym częstym wyjątkiem jest IndexOutOfBoundExceptionużycie nagłówka / stopki w ListView.

Więc rozwiązanie jest łatwe, po prostu kopiuję dane do adaptera z mojego źródła danych, gdy mój moduł obsługi wyzwala adapter, aby uzyskać dane i połączenia notifyDataSetChanged(). Katastrofa już nigdy się nie powtórzy.

HJWAJ
źródło
1
To był mój problem. W moim przypadku funkcja getCount () zwracała nowy licznik dostępny przed wywołaniem metody powiadomieńDataSetChanged. Ponieważ moja lista była „wirtualna”, teraz przechwytuję zaktualizowaną liczbę w metodzie powiadomieniaDataSetChanged, a następnie zwracam tę liczbę zapisaną w pamięci podręcznej, gdy zostaniesz zapytany o liczbę za pomocą getCount ().
Glenn
3

Gdyby tak się zdarzało, okazuje się, że miałem ten problem tylko wtedy, gdy lista była przewijana po kliknięciu „ładuj więcej” ostatniego elementu. Jeśli lista nie była przewijana, wszystko działało dobrze.

Po DUŻO debugowaniu był to błąd z mojej strony, ale także niespójność w kodzie Androida.

Po sprawdzeniu poprawności ten kod jest wykonywany w ListView

        } 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 "

Ale kiedy zdarza się onChange, uruchamia ten kod w AdapterView (element nadrzędny ListView)

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

Zauważ, że NIE ma gwarancji, że adapter będzie taki sam!

W moim przypadku, ponieważ był to „LoadMoreAdapter”, zwracałem WrappedAdapter w wywołaniu getAdapter (w celu uzyskania dostępu do podstawowych obiektów). Spowodowało to, że liczby były różne ze względu na dodatkowy element „Załaduj więcej” i zgłoszony wyjątek.

Zrobiłem to tylko dlatego, że dokumenty wydają się być w porządku

ListView.getAdapter javadoc

Zwraca adapter aktualnie używany w tym widoku listy. Zwrócony adapter może nie być tym samym adapterem przekazanym do setAdapter (ListAdapter), ale może być WrapperListAdapter.

aaronvargas
źródło
Mam podobny problem. Czy możesz zasugerować, jak to naprawić.
May13ank
Jeśli spojrzysz na 2 próbki kodu powyżej, zobaczysz mAdapter w ListView i getAdapter () w elemencie ListView (AdapterView). Jeśli zastąpisz getAdapter (), wynikiem getAdapter () może nie być mAdapter. Jeśli liczby 2 adapterów są różne, pojawia się błąd.
aaronvargas
3

Mój problem związany był z użyciem filtra razem z ListView.

Podczas ustawiania lub aktualizowania bazowego modelu danych ListView robiłem coś takiego:

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

Wywołanie filter()w ostatnim wierszu spowoduje (i musi) notifyDataSetChanged()zostać wywołane w publishResults()metodzie Filter . Czasami może to działać dobrze, szczególnie w moim szybkim Nexusie 5. Ale w rzeczywistości ukrywa błąd, który zauważysz w przypadku wolniejszych urządzeń lub w warunkach wymagających dużej ilości zasobów.

Problem polega na tym, że filtrowanie odbywa się asynchronicznie, a zatem między końcem filter()instrukcji a wywołaniem publishResults(), zarówno w wątku interfejsu użytkownika, inny kod wątku interfejsu użytkownika może zostać wykonany i zmienić zawartość adaptera.

Rzeczywista poprawka jest łatwa, wystarczy zadzwonić notifyDataSetChanged()także przed zażądaniem przeprowadzenia filtrowania:

public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
    this.allContacts = newContacts;
    this.filteredContacts = newContacts;
    notifyDataSetChanged(); // Fix
    getFilter().filter(filter);
}
cprcrack
źródło
3

Mam listę, jeśli obiekty Feed. Jest dołączany i obcinany z wątku bez interfejsu użytkownika. Działa dobrze z poniższym adapterem. W FeedAdapter.notifyDataSetChangedkażdym razie wywołuję wątek interfejsu użytkownika, ale nieco później. Podoba mi się to, ponieważ moje obiekty Feed pozostają w pamięci w usłudze lokalnej, nawet gdy interfejs użytkownika jest martwy.

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
źródło
2

Napotkałem ten sam problem z dokładnie tym samym dziennikiem błędów. W moim przypadku onProgress()AsyncTask dodaje wartości do adaptera za pomocą mAdapter.add(newEntry). Aby uniknąć mniejszej reakcji interfejsu użytkownika, ustawiam mAdapter.setNotifyOnChange(false)i dzwonię mAdapter.notifyDataSetChanged()4 razy w sekundę. Raz na sekundę tablica jest sortowana.

Działa to dobrze i wygląda bardzo uzależniająco, ale niestety można go zawiesić, dotykając wystarczająco często wyświetlanych elementów listy.

Ale wygląda na to, że znalazłem akceptowalne obejście. Domyślam się, że nawet jeśli pracujesz nad wątkiem interfejsu użytkownika, adapter nie akceptuje wielu zmian danych bez wywołania notifyDataSetChanged(), dlatego utworzyłem kolejkę, w której przechowywane są wszystkie nowe elementy, aż do końca wspomnianych 300 ms. Jeśli ten moment zostanie osiągnięty, dodaję wszystkie zapisane elementy do jednego strzału i zadzwonię notifyDataSetChanged(). Do tej pory nie byłem w stanie wykasować listy .

Lars K.
źródło
2

Nawet ja napotkałem ten sam problem w mojej aplikacji do powiadamiania XMPP, wiadomość o odbiornikach musi zostać dodana z powrotem do widoku listy (zaimplementowana z ArrayList). Gdy próbowałem dodać zawartość odbiornika przez MessageListener(osobny wątek), aplikacja kończy pracę z powyższym błędem. Rozwiązałem ten problem, dodając treść do mojej metody arraylist& setListviewadapaterthrough runOnUiThread, która jest częścią klasy Activity. To rozwiązało mój problem.

Balaji
źródło
1

Napotkałem podobny problem, oto jak rozwiązałem w moim przypadku. Sprawdzam, czy taskjuż jest, RUNNINGczy FINISHEDdlatego, że zadanie można uruchomić tylko raz. Poniżej zobaczysz częściowy i dostosowany kod z mojego rozwiązania.

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
źródło
1

Miałem ten sam problem i go rozwiązałem. Mój problem polegał na tym, że korzystałem listviewz adaptera macierzy i filtra. W metodzie performFilteringzadzierałem z tablicą zawierającą dane i to był problem, ponieważ ta metoda nie działa w wątku interfejsu użytkownika i EVENTUALLY powoduje pewne problemy.

Gusthema
źródło
1

Jedną z przyczyn tej awarii jest to, że ArrayListobiekt nie może się całkowicie zmienić. Więc kiedy usuwam element, muszę to zrobić:

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

To naprawiło awarię dla mnie.

andude
źródło
1

W moim przypadku wywołałem metodę GetFilter()na adapterze z TextWatcher()metody na głównej aktywności i dodałem dane z włączoną pętlą For GetFilter(). Rozwiązaniem była zmiana metody Pętla For na AfterTextChanged()podrzędną w głównym działaniu i usunięcie połączenia zGetFilter()

sek13300
źródło
1
Proszę wyjaśnić swoje rozwiązanie. Jestem również w sytuacji wygląda jak u /
nAkhmedov
1
adapter.notifyDataSetChanged()
Nithin Joseph
źródło
2
Dobrą praktyką dotyczącą przepełnienia stosu jest dodanie wyjaśnienia, dlaczego Twoje rozwiązanie powinno działać lub jest lepsze niż istniejące rozwiązania. Aby uzyskać więcej informacji, przeczytaj Jak odpowiedzieć .
Samuel Liew
0

Otrzymałem również ten sam błąd i korzystałem z AsyncTask:

`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

Rozwiązałem go, umieszczając adapter.notifyDataSetChanged();na dole mojego wątku interfejsu użytkownika, czyli mojej metody AsyncTask onPostExecute. Lubię to :

 protected void onPostExecute(Void aVoid) {

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

           adapter.notifyDataSetChanged();

                }

            });
        }

Teraz moja aplikacja działa.

EDYCJA: W rzeczywistości moja aplikacja ciągle się zawieszała co 1 na 10 razy, dając ten sam błąd.

W końcu natknąłem się runOnUiThreadna poprzedni post, który moim zdaniem może być przydatny. Więc umieściłem to w mojej metodzie doInBackground, jak poniżej:

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

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

I usunąłem adapter.notifyDataSetChanged();metodę. Teraz moja aplikacja nigdy się nie zawiesza.

CHARRIS
źródło
0

Wypróbuj jedno z następujących rozwiązań:

  1. Czasami, jeśli dodasz nowy obiekt do listy danych w wątku (lub doInBackgroundmetodzie), ten błąd wystąpi. Rozwiązaniem jest: utwórz listę tymczasową i dodaj dane do tej listy w wątku (lub doInBackground), a następnie skopiuj wszystkie dane z listy tymczasowej do listy adaptera w wątku interfejsu użytkownika (lub onPostExcute)

  2. Upewnij się, że wszystkie aktualizacje interfejsu użytkownika są wywoływane w wątku interfejsu użytkownika.

Phuc Tran
źródło
0

miałem ten sam problem, gdy dodałem nowe dane do leniwego programu ładującego obraz, który właśnie umieściłem

         adapter.notifyDataSetChanged();

w

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

mam nadzieję, że ci to pomoże

Hobii Sgonf
źródło
0

Jak powiedział @Mullins: „
Dodałem oba elementy i zadzwoniłem notifyDataSetChanged()w wątku interfejsu użytkownika i rozwiązałem to. - Mullins”.

W moim przypadku mam asynctaski zadzwoniłem notifyDataSetChanged()w doInBackground()sposobie i problem został rozwiązany, gdy zadzwoniłem z onPostExecute()Dostałem wyjątek.

Kopalnia Ciro
źródło
0

Miałem zwyczaj ListAdapteri dzwoniłem super.notifyDataSetChanged()na początku, a nie na końcu metody

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}
Pascalius
źródło
0

Miałem to samo ustawienie, miałem wiele buttongroup insite mojego elementu w widoku listy i zmieniałem niektóre wartości logiczne w moim elemencie, takie jak holder.rbVar.setOnclik ...

mój problem wystąpił, ponieważ wywoływałem metodę wewnątrz getView (); i zapisywałem obiekt w preferencjach udostępniania, więc miałem ten sam błąd powyżej

Jak to rozwiązałem; Usunąłem metodę w getView (), aby powiadomićDataSetInvalidated () i problem zniknął

   @Override
    public void notifyDataSetChanged() {
        saveCurrentTalebeOnShare(currentTalebe);
        super.notifyDataSetChanged();
    }
Sam
źródło
0

miałem ten sam problem. w końcu mam rozwiązanie

przed zaktualizowaniem widoku listy, jeśli klawiatura programowa jest obecna, zamknij ją najpierw. po tym ustaw źródło danych i wywołaj notifydatasetchanged ().

podczas wewnętrznego zamykania klawiatury widok listy zaktualizuje interfejs użytkownika. dzwoni do zamknięcia klawiatury. w tym czasie zmiana źródła danych spowoduje zgłoszenie tego wyjątku. jeśli dane są aktualizowane w onActivityResult, istnieje szansa na ten sam błąd.

 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
źródło
0

Moje rozwiązanie:

1) utwórz temp ArrayList .

2) wykonuj ciężkie prace (pobieranie wiersza sqlite, ...) w doInBackground metodą i dodawaj elementy do tablicy temp.

3) w onPostExecutesposób metodyczny dodaj wszystkie elementy z temp araylist do arraylisty listy widoków .

note:możesz chcieć usunąć niektóre elementy z widoku listy, a także usunąć z bazy danych sqlite, a może usunąć niektóre pliki związane z elementami z sdcard, po prostu usuń elementy z bazy danych i usuń powiązane pliki i dodaj je do tymczasowej tablicy background thread. następnie UI threadusuń elementy istniejące w temp arraylist z arraylist listy widoków.

Mam nadzieję że to pomoże.

Nikt 8
źródło