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 notifyDataSetChanged
co 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ął.
Odpowiedzi:
Miałem ten sam problem.
Dodawałem elementy do mojego
ArrayList
poza wątkiem interfejsu użytkownika.Rozwiązanie: Zrobiłem oba
adding the items
i zadzwoniłemnotifyDataSetChanged()
w wątku interfejsu użytkownika.źródło
Miałem ten sam problem, ale naprawiłem go metodą
z klasy
ListView
źródło
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.
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ć.
Mam nadzieję że to pomoże
źródło
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:
źródło
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.
źródło
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)
irequestLayout()
, ale liczba awarii tylko nieznacznie spada.I w końcu to rozwiązałem. Nic z tym
setVisibility(GONE/VISIBLE)
. Nic z tymrequestLayout()
.W końcu znalazłem powód, dla którego użyłem
Handler
wywołanianotifyDataSetChanged()
po aktualizacji danych, co może prowadzić do:checkForTap()
/onTouchEvent()
i ostatecznie wywołaćlayoutChildren()
)notifyDataSetChanged()
oraz widoków połączeń i aktualizacjiI popełniłem kolejny błąd
getCount()
,getItem()
i w , igetView()
bezpośrednio używam pól w DataSource, zamiast kopiować je do adaptera. Więc w końcu ulega awarii, gdy:getCount()
igetView()
jest wywoływany, a widok listy znajduje dane niespójne i zgłasza wyjątki, takie jakjava.lang.IllegalStateException: The content of the adapter has changed but...
. Innym częstym wyjątkiem jestIndexOutOfBoundException
użycie nagłówka / stopki wListView
.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.źródło
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
Ale kiedy zdarza się onChange, uruchamia ten kod w AdapterView (element nadrzędny ListView)
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
źródło
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:
Wywołanie
filter()
w ostatnim wierszu spowoduje (i musi)notifyDataSetChanged()
zostać wywołane wpublishResults()
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łaniempublishResults()
, 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:źródło
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.notifyDataSetChanged
każ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.źródło
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, ustawiammAdapter.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 .źródło
Jest to znany błąd w Androidzie 4 do 4.4 (KitKat) i został rozwiązany w „> 4.4”
Zobacz tutaj: https://code.google.com/p/android/issues/detail?id=71936
źródło
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 przezMessageListener
(osobny wątek), aplikacja kończy pracę z powyższym błędem. Rozwiązałem ten problem, dodając treść do mojej metodyarraylist
&setListviewadapater
throughrunOnUiThread
, która jest częścią klasy Activity. To rozwiązało mój problem.źródło
Napotkałem podobny problem, oto jak rozwiązałem w moim przypadku. Sprawdzam, czy
task
już jest,RUNNING
czyFINISHED
dlatego, że zadanie można uruchomić tylko raz. Poniżej zobaczysz częściowy i dostosowany kod z mojego rozwiązania.źródło
Miałem ten sam problem i go rozwiązałem. Mój problem polegał na tym, że korzystałem
listview
z adaptera macierzy i filtra. W metodzieperformFiltering
zadzierał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.źródło
Jedną z przyczyn tej awarii jest to, że
ArrayList
obiekt nie może się całkowicie zmienić. Więc kiedy usuwam element, muszę to zrobić:To naprawiło awarię dla mnie.
źródło
W moim przypadku wywołałem metodę
GetFilter()
na adapterze zTextWatcher()
metody na głównej aktywności i dodałem dane z włączoną pętlą ForGetFilter()
. Rozwiązaniem była zmiana metody Pętla For naAfterTextChanged()
podrzędną w głównym działaniu i usunięcie połączenia zGetFilter()
źródło
źródło
Otrzymałem również ten sam błąd i korzystałem z AsyncTask:
Rozwiązałem go, umieszczając
adapter.notifyDataSetChanged();
na dole mojego wątku interfejsu użytkownika, czyli mojej metody AsyncTask onPostExecute. Lubię to :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ę
runOnUiThread
na poprzedni post, który moim zdaniem może być przydatny. Więc umieściłem to w mojej metodzie doInBackground, jak poniżej:I usunąłem
adapter.notifyDataSetChanged();
metodę. Teraz moja aplikacja nigdy się nie zawiesza.źródło
Wypróbuj jedno z następujących rozwiązań:
Czasami, jeśli dodasz nowy obiekt do listy danych w wątku (lub
doInBackground
metodzie), ten błąd wystąpi. Rozwiązaniem jest: utwórz listę tymczasową i dodaj dane do tej listy w wątku (lubdoInBackground
), a następnie skopiuj wszystkie dane z listy tymczasowej do listy adaptera w wątku interfejsu użytkownika (lubonPostExcute
)Upewnij się, że wszystkie aktualizacje interfejsu użytkownika są wywoływane w wątku interfejsu użytkownika.
źródło
miałem ten sam problem, gdy dodałem nowe dane do leniwego programu ładującego obraz, który właśnie umieściłem
w
mam nadzieję, że ci to pomoże
źródło
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
asynctask
i zadzwoniłemnotifyDataSetChanged()
wdoInBackground()
sposobie i problem został rozwiązany, gdy zadzwoniłem zonPostExecute()
Dostałem wyjątek.źródło
Miałem zwyczaj
ListAdapter
i dzwoniłemsuper.notifyDataSetChanged()
na początku, a nie na końcu metodyźródło
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ął
źródło
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.
źródło
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
onPostExecute
sposó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 tablicybackground thread
. następnieUI thread
usuń elementy istniejące w temp arraylist z arraylist listy widoków.Mam nadzieję że to pomoże.
źródło