Android, ListView IllegalStateException: "Вміст адаптера змінився, але ListView не отримав сповіщення"


189

Що я хочу зробити : запустити фонову нитку, яка обчислює вміст ListView та частково оновити ListView, тоді як результати обчислюються.

Чого я знаю, я мушу уникати : я не можу возитися зі змістом ListAdapter з фонової нитки, тому я успадкував AsyncTask і опублікував результат (додайте записи до адаптера) від onProgressUpdate. Мій адаптер використовує ArrayList об'єктів результатів, всі операції над цими масивами синхронізовані.

Дослідження інших людей : є дуже цінні дані тут . Я також зазнав майже щоденних збоїв для групи з ~ 500 користувачів, і коли я додав list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)блок в onProgressUpdate, збої знижуються в 10 разів, але не зникають. (було запропоновано у відповідь )

Що мені трапляється іноді : зверніть увагу, це трапляється дуже рідко (раз на тиждень для одного з 3,5 тис. Користувачів). Але я хотів би позбутися цієї помилки повністю. Ось частковий стек-трек:

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

Допомога? Більше не потрібно, дивіться нижче

ЗАКЛЮЧНИЙ ВІДПОВІДЬ: Як виявилося, я телефонував notifyDataSetChangedкожні 5 вставок, щоб уникнути мерехтіння та раптових змін у списку. Це неможливо зробити так, завжди повідомляйте адаптер про зміну базового списку. Зараз ця помилка давно пішла для мене.


3
Ви дзвонили notifyDataSetChanged ()?
Денис Пальницький

1
Звичайно, в onProgressUpdate йде послідовність: list.setVisibility (GONE) - addObjectToList / синхронізована операція у списку / - notifyDataSetChanged () - list.setVisibility (VISIBLE) (і без змін видимості виняток трапляється набагато частіше)
tomash

1
Ви змінюєте базовий ArrayList для іншого потоку? Усі зміни, навіть у ArrayList, на який посилається Адаптер, повинні відбуватися на потоці інтерфейсу користувача.
Багатий Шулер

2
@Qberticus - як я чітко заявив, я не змінюю ArrayList з іншого потоку, а з методу onProgressUpdate AcyncTask - він працює в потоці GUI.
tomash

1
@tomash Я отримав те саме виняток, що я використовую "pull", щоб оновити adapter.notifyDataSetChanged (); lvAutherlist.completeRefreshing (); але іноді трапляється ця помилка, як її вирішити
Хан

Відповіді:


119

У мене було те саме питання.

Я додавав елементи до моїх ArrayListпотоків інтерфейсу користувача.

Рішення: я зробив і те, adding the itemsі викликав notifyDataSetChanged()потік інтерфейсу користувача.


4
Я обоє додав елементи та зателефонував notifyDataSetChanged () у потік інтерфейсу, і я вирішив це.
Маллінз

23
Геніальний коментар, дійсно.
дентекс

2
Це багатопотокове видання та використання належним чином синхронізованих блоків. Це можна запобігти. Не ставлячи зайвих речей на UI Thread і не спричиняючи втрати чуйності програми. Перевірте мою відповідь нижче.
Яванатор

2
Для майбутніх читачів - робити обчислення у фоновому потоці - це добре, але вам слід передати результати в потоці інтерфейсу користувача та виконати додавання елементів до базової колекції адаптера та адаптера сповіщення у тому ж блоці коду.
tomash

4
@Mullins Чи можете ви, будь ласка, показати зразок для свого рішення? Я застряг у тій же проблемі
Jas

27

У мене була така ж проблема, але я її виправив за допомогою методу

requestLayout();

з класу ListView


35
коли ми повинні називати цей метод?
JehandadK

3
@DeBuGGeR після додавання елементів до ListView або зміни адаптера.
Ahmet Noyan Kızıltan

Що робити, якщо я додаю елементи в асинтактаск
д-р aNdRO

2
Тільки для запису @ Dr.aNdRO, ви збираєте свої результати в AsyncTask. doInBackground () і збережіть ваші результати для оновлення списку в AsyncTask.onPostExecute (), який запуститься в потоці інтерфейсу користувача. Якщо вам потрібно оновити під час переходу, використовуйте AsyncTask.publishProgress () та AsyncTask.onProgressUpdate (), який також працює в потоці інтерфейсу користувача.
Ніколь Борреллі

21

Це питання з багатопотоковим записом та використання належно синхронізованого блоків. Це можна запобігти. Не ставлячи зайвих речей на інтерфейс UI і не втрачаючи чутливості програми.

Я теж зіткнувся з тим же. Оскільки найприйнятніша відповідь пропонує змінити адаптерні дані з UI Thread, це може вирішити проблему. Це буде працювати, але це швидке і просте рішення, але не найкраще.

Як ви бачите для звичайного випадку. Оновлення адаптера даних з фонового потоку та виклику notifyDataSetChanged в потоці інтерфейсу користувача працює.

Ця незаконнаStateException виникає, коли потік ui оновлює подання, а інший фоновий потік знову змінює дані. Цей момент викликає це питання.

Тож якщо ви будете синхронізувати весь код, який змінює дані адаптера і здійснювати notifydatasetchange call. Цього питання не слід пропадати. Як пішло для мене, і я все ще оновлюю дані з фонового потоку.

Ось мій специфічний для конкретного випадку код, на який посилаються інші

Мій завантажувач на головному екрані завантажує контакти телефонної книги у мої джерела даних у фоновому режимі.

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

Цей PhoneBookManager.getPhoneBookContacts читає контакти з телефонної книги та заповнює їх у хеш-картах. Що можна використовувати безпосередньо для адаптації списку для складання списку.

На моєму екрані є кнопка. Це відкриває діяльність, де вказані ці телефонні номери. Якщо я безпосередньо встановлю адаптер над списком до того, як попередній потік закінчить свою роботу, то швидкий випадок налаштування трапляється рідше. Це спливає виняток. Яка назва цього питання. Тож я маю зробити щось подібне у другій діяльності.

Мій завантажувач під час другої активності чекає завершення першого потоку. Поки не відображається смуга прогресу. Перевірте loadInBackground обох навантажувачів.

Потім він створює адаптер і доставляє його до тієї діяльності, де в потоці ui я викликаю setAdapter.

Це вирішило моє питання.

Цей код є лише фрагментом. Вам потрібно змінити його, щоб добре скласти для вас.

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

}

Сподіваюся, це допомагає


Вибачте, як ти це зробив? Як ви синхронізували фоновий код потоку та виклик notifydatasetchange? Я використовую doInBackground під час фільтрації даних у AutocompleteTextView, і я зіткнувся з тією ж проблемою ... Не могли б ви порадити мені щось для вирішення?
tonix

@ user3019105 - Синхронізація двох фонових потоків. той, який оновлює дані у фоновому режимі, та інший, який сповіщає потік ui для встановленняAdadpter або notifydatadatanged за допомогою методів handler.post або методів runOnUiThread. У моєму конкретному випадку я синхронізував два навантажувачі з синхронізованими на однотонному об'єкті doInBackground. Почніть з підготовки даних на самому екрані заставки, щоб вони були швидко доступними для екрана приладової панелі. Це була моя потреба.
Яванатор

Чи можете ви опублікувати фрагмент коду цієї програми? У моєму випадку я вирішив викликати clear () на адаптері, створивши тимчасовий ArrayList <T> відфільтрованих об'єктів, потім здійснив виклик до addAll (tmpArrayList) і нарешті notifyDataSetChanged (). Я роблю все це заклики всередині методу FilterResult (), працює на головній темі. Як ви вважаєте, це надійне рішення?
tonix

@ user3019105 перевірити відредаговану відповідь. Сподіваюсь, вам зараз корисно
Javanator

ця відповідь вимагає багато читати про те, чому вона працює, tutorials.jenkov.com/java-concurrency/synchronized.html - це гарне місце для початку
14:22

15

Я вирішив це за допомогою 2 списків. Один список я використовую лише для адаптера, і я роблю всі зміни / оновлення даних в іншому списку. Це дозволяє мені робити оновлення для одного списку у фоновому потоці, а потім оновлювати список "адаптер" у головному потоці / інтерфейсі користувача:

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

7

Я написав цей код і мав його працювати у зображенні емулятора 2.1 протягом ~ 12 годин і не отримав IllegalStateException. Я збираюся дати андроїд-рамки користь сумнівів у цьому і скажу, що це, швидше за все, помилка у вашому коді. Я сподіваюся, що це допомагає. Можливо, ви можете адаптувати його до свого списку та даних.

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

Дякую за вашу роботу! Я помітив, що для методу ArrayAdapter hasStableIds () = false; моя реалізація повернула true, що було не зовсім правильно, оскільки рядки були відсортовані перед кожним notifyDataSetChanged (). Я спробую - якщо аварії виживуть, я продовжуватиму розслідування. З найкращими побажаннями!
tomash

4

Кілька днів тому я зіткнувся з тією ж проблемою і спричиняє кілька тисяч аварій на день, приблизно 0,1% користувачів стикаються з цією ситуацією. Я спробував setVisibility(GONE/VISIBLE)і requestLayout(), але відлік аварії тільки трохи знижується.

І я нарешті це вирішив. Нічого з setVisibility(GONE/VISIBLE). Нічого з requestLayout().

Нарешті я виявив причину - я використовував Handlerдзвінок notifyDataSetChanged()після оновлення даних, що може призвести до свого роду:

  1. Оновлення даних до об'єкта моделі (я називаю це DataSource)
  2. Користувач торкається перегляду списку (який може дзвонити checkForTap()/ onTouchEvent()і нарешті дзвонити layoutChildren())
  3. Адаптер отримує дані з модельного об'єкта, перегляди дзвінків notifyDataSetChanged()та оновлення

І я зробив ще одну помилку, що в getCount(), getItem()і getView()я безпосередньо використовую поля в DataSource, а не копіюю їх у адаптер. Тож нарешті він виходить з ладу, коли:

  1. Адаптер оновлює дані, які дає остання відповідь
  2. Коли наступна відповідь повертається, DataSource оновлює дані, що призводить до зміни кількості елементів
  3. Користувач торкається перегляду списку, який може бути натисканням або переміщенням або перевертанням
  4. getCount()і getView()називається, і listview знаходить, що дані не є послідовними, і викидає такі винятки java.lang.IllegalStateException: The content of the adapter has changed but.... Іншим поширеним винятком є IndexOutOfBoundExceptionякщо ви використовуєте колонтитул / колонтитул у ListView.

Тому рішення легко, я просто копіюю дані в адаптер зі свого DataSource, коли мій обробник спрацьовує адаптер для отримання даних та дзвінків notifyDataSetChanged(). Аварія зараз більше ніколи не повторюється.


1
Це було моє питання. У моєму випадку getCount () повертав нову кількість доступних до виклику notifyDataSetChanged. Оскільки мій список був "віртуальним", тепер я фіксую оновлений підрахунок у своєму методі notifyDataSetChanged, а потім повертаю цей кешований рахунок, коли його запитують про рахунок через getCount ().
Гленн

3

Якщо це траплялося з перервами, виявляється, у мене виникла ця проблема лише тоді, коли список прокручувався після натискання останнього елемента "Завантажити більше". Якщо список не прокручувався, все працювало нормально.

Після ВІЛЬСЬКОЇ налагодження, з моєї сторони це була помилка, але також невідповідність коду Android.

Коли відбувається перевірка, цей код виконується в 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 "

Але коли onChange трапляється, він запускає цей код у AdapterView (батько ListView)

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

Зауважте, що адаптер НЕ гарантовано буде однаковим!

У моєму випадку, оскільки це був "LoadMoreAdapter", я повертав WrappedAdapter у виклику getAdapter (для доступу до основних об'єктів). Це призвело до того, що підрахунки відрізняються через додатковий елемент "Завантажити більше" та викид "Винятку".

Я зробив це лише тому, що в документах це здається, що це нормально робити

ListView.getAdapter javadoc

Повертає адаптер, який зараз використовується у цьому ListView. Повернений адаптер може бути не тим самим адаптером, який передано setAdapter (ListAdapter), але може бути WrapperListAdapter.


У мене є подібне питання. Чи можете ви підказати, як це виправити.
13

Якщо ви подивитесь на 2 зразки коду вище, ви побачите mAdapter у ListView та getAdapter () у батьківському списку ListView (AdapterView). Якщо ви перекриєте getAdapter (), результатом getAdapter () може бути не mAdapter. Якщо кількість 2 адаптерів відрізняється, ви отримуєте помилку.
aaronvargas

3

Моя проблема стосувалася використання фільтра разом із ListView.

Під час налаштування чи оновлення основної моделі даних ListView я робив щось подібне:

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

Виклик filter()в останньому рядку викликає (і повинен) викликати notifyDataSetChanged()у publishResults()методі фільтра . Іноді це може спрацювати нормально, особливо в моєму швидкому Nexus 5. Але насправді це приховує помилку, яку ви помітите при повільніших пристроях або в умовах, що потребують ресурсів.

Проблема полягає в тому, що фільтрація проводиться асинхронно, і, таким чином, між кінцем filter()оператора і викликом до publishResults()обох в потоці інтерфейсу користувача, якийсь інший код потоку інтерфейсу може виконувати та змінювати вміст адаптера.

Дійсне виправлення легко, просто зателефонуйте notifyDataSetChanged()також перед тим, як вимагати виконання фільтрації:

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

3

У мене є список, якщо Feed об'єктів. Він додається та скорочується з потоку, що не використовує інтерфейс користувача. Він добре працює з адаптером нижче. Я закликаю FeedAdapter.notifyDataSetChangedпотік інтерфейсу все одно, але трохи пізніше. Мені це подобається, тому що мої об'єкти Feed залишаються в пам'яті в локальній службі навіть тоді, коли інтерфейс помер.

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

2

Я зіткнувся з тією ж проблемою з точно тим же журналом помилок. У моєму випадку onProgress()AsyncTask додає значення адаптеру за допомогою mAdapter.add(newEntry). Щоб інтерфейс не став менш чуйним, я встановлюю mAdapter.setNotifyOnChange(false)і дзвонюmAdapter.notifyDataSetChanged() 4 рази в секунду. Раз на секунду масив сортується.

Це добре працює і виглядає дуже захоплюючою, але, на жаль, його можна зламати, торкаючись показаних елементів списку досить часто.

Але, схоже, я знайшов прийнятне рішення. Я здогадуюсь, що навіть якщо ви просто працюєте над потоком інтерфейсу, адаптер не приймає багато змін до своїх даних без виклику notifyDataSetChanged(), через це я створив чергу, яка зберігає всі нові елементи, поки не закінчиться згадані 300 мс. Якщо цей момент досягнуто, я додаю всі збережені предмети за один кадр і дзвоню notifyDataSetChanged(). До цих пір я не зміг зірвати список .



2

Навіть я зіткнувся з тією ж проблемою в моєму додатку XMPP-сповіщення, повідомлення приймачів потрібно додати до переліку списку (реалізованого разом із ArrayList). Коли я намагався додати вміст приймача через MessageListener(окремий потік), програма закриває помилку вище. Я вирішив цю проблему, додавши зміст до моєї arraylistі setListviewadapaterпо runOnUiThreadметоду , який є частиною класу Activity. Це вирішило мою проблему.


1

Я зіткнувся з подібною проблемою, ось як я вирішив у своїй справі. Я переконуюсь, що taskвже є RUNNINGчи FINISHEDтому що завдання може виконуватися лише один раз. Нижче ви побачите частковий і адаптований код з мого рішення.

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

}

}

1

У мене була така ж проблема, і я її вирішив. Моя проблема полягала в тому, що я використовував listviewадаптер масиву та фільтр. У методі performFilteringя зіпсувався з масивом, у якого є дані, і це була проблема, оскільки цей метод не працює на потоці інтерфейсу, і ВЗАЄМО це викликає деякі проблеми.


1

Однією з причин цієї аварії є те, що ArrayListоб'єкт не може змінитися повністю. Отже, коли я виймаю предмет, я повинен це зробити:

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

Це виправило крах для мене.


1

У моєму випадку я зателефонував методу GetFilter()на адаптер із TextWatcher()методу основної діяльності, і я додав дані за допомогою циклу For GetFilter(). Рішенням було змінити цикл For для AfterTextChanged()підпорядкування в основній діяльності та видалити виклик доGetFilter()


1
Будь ласка, уточніть своє рішення. Я також в ситуації виглядає як u /
nAkhmedov


0

Я також отримував абсолютно таку ж помилку і використовував 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

Я вирішив це, поставивши adapter.notifyDataSetChanged();внизу моєї потоку інтерфейсу користувача, що є моїм методом AsyncTask onPostExecute. Подобається це :

 protected void onPostExecute(Void aVoid) {

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

           adapter.notifyDataSetChanged();

                }

            });
        }

Зараз моє додаток працює.

EDIT: Насправді мій додаток все-таки виходив з ладу приблизно кожні 1 раз у 10 разів.

Врешті-решт я натрапив runOnUiThreadна попередній пост, який я вважав, що може бути корисним. Тому я вклав це у свій метод doInBackground, як це:

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

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

І я видалив adapter.notifyDataSetChanged();метод. Тепер моє додаток ніколи не виходить з ладу.


0

Спробуйте одне з таких рішень:

  1. Іноді, якщо ви додаєте новий об’єкт до списку даних у потоці (або doInBackgroundметоді), ця помилка виникне. Рішення полягає в тому, щоб створити тимчасовий список і додати дані до цього списку в потоці (або doInBackground), після чого виконайте копіювання всіх даних з тимчасового списку до списку адаптера в потоці інтерфейсу (або onPostExcute)

  2. Переконайтесь, що всі оновлення користувальницького інтерфейсу викликаються в потоці користувальницького інтерфейсу.


0

У мене була така ж проблема, коли я додаю нові дані в ледачий завантажувач зображень, який я просто помістив

         adapter.notifyDataSetChanged();

в

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

сподіваюся, що тобі це допоможе


0

Як @Mullins сказав: "
Я обоє додав елементи та подзвонив notifyDataSetChanged()у потік інтерфейсу, і я вирішив це. - Маллінз".

У моєму випадку я маю, asynctaskі я зателефонував notifyDataSetChanged()у doInBackground()методі, і проблема вирішується, коли я подзвонив від onPostExecute()мене, я отримав виняток.


0

Я мав звичай ListAdapterі дзвонив super.notifyDataSetChanged()на початку, а не в кінці методу

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

0

У мене була така ж ситуація, у мене було багато бутонових груп, які наполягали на моєму елементі в списку перегляду, і я змінював деякі булі значення в моєму елементі, як hold.rbVar.setOnclik ...

моя проблема виникла через те, що я викликав метод всередині getView (); і зберігав об’єкт всередині sharepreference, тому я мав таку ж помилку вище

Як я це вирішив; Я видалив свій метод всередині getView (), щоб сповіститиDataSetInvalidated (), і проблема усунена

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

0

у мене була така ж проблема. нарешті я отримав рішення

перед оновленням перегляду списку, якщо наявна м'яка клавіатура, спочатку закрийте її. після цього встановіть джерело даних і виклик notifydatasetchanged ().

під час закриття клавіатури внутрішній перегляд списку оновить його інтерфейс. він продовжує дзвонити до закриття клавіатури. того часу, якщо джерело даних зміниться, воно скине цей виняток. якщо дані оновлюються в onActivityResult, є ймовірність тієї ж помилки.

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

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

0

Моє рішення:

1) створити temp ArrayList.

2) виконайте ваші важкі роботи (sqlite ret row fetch, ...) у doInBackgroundметоді та додайте елементи до temp arraylist.

3) додайте всі елементи з темп-араїліста до арабського списку списку списку onPostExecuteметодів.

note:ви можете видалити деякі елементи з списку перегляду, а також видалити з бази даних sqlite і, можливо, видалити деякі файли, пов’язані з елементами з sdcard, просто видаліть елементи з бази даних та видаліть пов’язані з ними файли та додайте їх до тимчасового масиву background thread. то вUI thread видаліть елементи, наявні в temp arraylist, із списку array.

Сподіваюся, це допомагає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.