Android, ListView IllegalStateException: “The content of the adapter has changed but ListView did not receive a notification”

后端 未结 25 2113
执念已碎
执念已碎 2020-11-22 16:59

What I want to do: run a background thread which calculates ListView contents and update ListView partially, while results are calculated.

W

相关标签:
25条回答
  • 2020-11-22 17:29

    In my case I called the method GetFilter() on an adapter from the TextWatcher() method on main Activity, and I added the data with a For loop on GetFilter(). The solution was change the For loop to AfterTextChanged() sub method on main Activity and delete the call to GetFilter()

    0 讨论(0)
  • 2020-11-22 17:31
    adapter.notifyDataSetChanged()
    
    0 讨论(0)
  • 2020-11-22 17:32

    I wrote this code and had it run in a 2.1 emulator image for ~12 hours and did not get the IllegalStateException. I'm going to give the android framework the benefit of the doubt on this one and say that it is most likely an error in your code. I hope this helps. Maybe you can adapt it to your list and data.

    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);
        }
    }
    
    0 讨论(0)
  • 2020-11-22 17:34

    Had this happen intermittently, turns out I only had this issue when the list was scrolled after a 'load more' last item was clicked. If the list wasn't scrolled, everything worked fine.

    After MUCH debugging, it was a bug on my part, but an inconsistency in the Android code also.

    When the validation happens, this code is executed in 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 "
    

    But when onChange happens it fires this code in AdapterView (parent of ListView)

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

    Notice the way the Adapter is NOT guaranteed to be the Same!

    In my case, since it was a 'LoadMoreAdapter' I was returning the WrappedAdapter in the getAdapter call (for access to the underlying objects). This resulted in the counts being different due to the extra 'Load More' item and the Exception being thrown.

    I only did this because the docs make it seem like it's ok to do

    ListView.getAdapter javadoc

    Returns the adapter currently in use in this ListView. The returned adapter might not be the same adapter passed to setAdapter(ListAdapter) but might be a WrapperListAdapter.

    0 讨论(0)
  • 2020-11-22 17:34

    Even I faced the same problem in my XMPP notification application, receivers message needs to be added back to list view (implemented with ArrayList). When I tried to add the receiver content through MessageListener (separate thread), application quits with above error. I solved this by adding the content to my arraylist & setListviewadapater through runOnUiThread method which is part of Activity class. This solved my problem.

    0 讨论(0)
  • 2020-11-22 17:34

    I had the same problem and I solved it. My problem was that I was using a listview, with an array adapter and with filter. On the method performFiltering I was messing with the array that have the data and it was the problem since this method is not running on the UI thread and EVENTUALLY it raises some problems.

    0 讨论(0)
提交回复
热议问题