代码之家  ›  专栏  ›  技术社区  ›  tomash

android,listview illegalstateexception:“适配器的内容已更改,但listview未收到通知”

  •  178
  • tomash  · 技术社区  · 16 年前

    What I want to do :运行一个后台线程,在计算结果时计算ListView内容并部分更新ListView。

    What I know I have to avoid :我无法处理来自后台线程的ListAdapter内容,因此我从OnProgressUpdate继承了AsyncTask和发布结果(向适配器添加条目)。我的适配器使用结果对象的arraylist,这些arraylist上的所有操作都是同步的。

    他人研究 : there is very valuable data here . I also suffered from almost daily crashes for group of ~500 users, and when I added list.setVisibility(GONE)/trackList.setVisibility(VISIBLE) 在OnProgressUpdate中,崩溃降低了10倍,但没有消失。(建议在 answer )

    我有时得到的 :请注意,这种情况很少发生(3.5K用户中的一个每周发生一次)。But I'd like to get rid of this bug completely. 以下是部分stacktrace:

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

    帮助? Not needed anymore, see below

    最终答案: As it turned out, I was calling notifyDataSetChanged 每5次插入,以避免闪烁和突然的列表更改。不能这样做,当基列表更改时总是通知适配器。这个虫子现在已经不见了。

    24 回复  |  直到 7 年前
        1
  •  112
  •   Uday Koushik Mullins    9 年前

    I had the same issue.

    I was adding items to my ArrayList outside the UI thread.

    Solution: I have done both, adding the items 并称之为 notifyDataSetChanged() in the UI thread.

        2
  •  26
  •   Charuක    9 年前

    I had the same problem, but I fixed it using the method

    requestLayout();
    

    从班里 ListView

        3
  •  20
  •   Rohit Sharma    12 年前

    这是一个 多线程 正确发放和使用 同步的 Blocks This can be prevented. Without putting extra things on UI Thread and causing loss of responsiveness of app.

    I also faced the same. And as the most accepted answer suggests making change to adapter data from UI Thread can solve the issue. That will work but is a quick and easy solution but not the best one.

    As you can see for a normal case. Updating data adapter from background thread and calling notifyDataSetChanged in UI thread works.

    This illegalStateException arises when a ui thread is updating the view and another background thread changes the data again. That moment causes this issue.

    So if you will synchronize all the code which is changing the adapter data and making notifydatasetchange call. This issue should be gone. As gone for me and i am still updating the data from background thread.

    Here is my case specific code for others to refer.

    My loader on the main screen loads the phone book contacts into my data sources in the background.

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

    This PhoneBookManager.getPhoneBookContacts reads contact from phonebook and fills them in the hashmaps. Which is directly usable for List Adapters to draw list.

    There is a button on my screen. That opens a activity where these phone numbers are listed. 如果在前一个线程完成它的工作之前直接将适配器设置在列表上,那么快速的NaviaGeSE案例就更少发生。它会弹出异常,这就是这个问题的标题。所以我必须在第二个活动中这样做。

    Then it creates the adapter and deliver it to the activity where on ui thread i call setAdapter.

    That solved my issue.

    This code is a snippet only. You need to change it to compile well for you.

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

    希望这有帮助

        4
  •  15
  •   triad    12 年前

    我通过2个列表来解决这个问题。一个列表,我只使用适配器,我做的所有数据更改/更新其他列表。这允许我在后台线程中对一个列表进行更新,然后更新主/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();
            }
        });
    }
    
        5
  •  7
  •   Rich Schuler    16 年前

    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);
        }
    }
    
        6
  •  3
  •   aaronvargas    12 年前

    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.

    经过多次调试,我觉得这是个错误,但Android代码中也有不一致之处。

    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!

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

    ListView.getAdapter javadoc

    返回此ListView中当前使用的适配器。归还的人 适配器可能不是传递到的适配器 设置适配器(ListAdvor),但可能是一个RabPielListAdvor。

        7
  •  3
  •   cprcrack    12 年前

    我的问题与使用A有关。 Filter together with the ListView.

    When setting or updating the underlying data model of the ListView, I was doing something like this:

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

    打电话 filter() in the last line will (and must) cause notifyDataSetChanged() 在过滤器中调用 publishResults()

    The problem is that the filtering is done asynchronously, and thus between the end of the 过滤器() 语句和调用 发布结果() 在UI线程中,其他一些UI线程代码可能会执行并更改适配器的内容。

    The actual fix is easy, just call notifyDataSetChanged() 在请求进行过滤之前:

    public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
    {
        this.allContacts = newContacts;
        this.filteredContacts = newContacts;
        notifyDataSetChanged(); // Fix
        getFilter().filter(filter);
    }
    
        8
  •  3
  •   Charuක    9 年前

    如果有进给对象,我有一个列表。 它是从没有UI线程追加和截断的。 It works fine with adapter below. 我打电话 FeedAdapter.notifyDataSetChanged in UI thread anyway but little bit later. I do like this because my Feed objects stay in memory in Local Service even when UI is dead.

    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;
        }
    }
    
        9
  •  3
  •   HJWAJ    9 年前

    几天前,我遇到了同样的问题,每天造成数千次崩溃,大约0.1%的用户遇到了这种情况。我试过 setVisibility(GONE/VISIBLE) requestLayout() , but crash count only decreases a little.

    And I finally solved it. 什么都没有 setVisibility(GONE/VISIBLE) . 什么都没有 请求程序输出() .

    Finally I found the reason is I used a Handler 打电话 notifyDataSetChanged() after update data, which may lead to a sort of:

    1. Updates data to a model object(I call it a DataSource)
    2. User touches listview(which may call checkForTap() / onTouchEvent() 最后打电话 layoutChildren() )
    3. Adapter gets data from model object and call notifyDataSetChanged() and update views

    我又犯了一个错误 getCount() , getItem() getView() ,我直接使用数据源中的字段,而不是将它们复制到适配器。最后,当:

    1. Adapter updates data which last response gives
    2. 当下一个响应返回时,数据源更新数据,这会导致项目计数更改。
    3. User touches listview, which may be a tap or a move or flip
    4. GETCONTUTH() GETVIEW() 调用,并且ListView发现数据不一致,并引发异常,如 java.lang.IllegalStateException: The content of the adapter has changed but... . 另一个常见的例外是 IndexOutOfBoundException 如果在中使用页眉/页脚 ListView .

    所以解决方案很简单,当我的处理程序触发适配器获取数据和调用时,我只需将数据从数据源复制到适配器。 . 撞车事故现在再也不会发生了。

        10
  •  2
  •   Lars K.    15 年前

    我正面临着同样的问题与完全相同的错误日志。 以我为例 onProgress() 将值添加到适配器 mAdapter.add(newEntry) . 为了避免UI变得更少响应,我设置 mAdapter.setNotifyOnChange(false) 并打电话 mAdapter.notifyDataSetChanged() 是第二次的4倍。数组每秒排序一次。

    这项功能很好,看起来非常令人上瘾,但不幸的是,经常触摸所显示的列表项可能会使其崩溃。

    我想,即使您只是在UI线程上工作,适配器也不会在不调用的情况下接受对其数据的许多更改。 notifyDataSetChanged() 因此,我创建了一个队列,存储所有新项目,直到上述300毫秒结束。如果到了这个时候,我会一次添加所有存储的项目,然后调用 通知数据集更改() . 直到现在 .

        11
  •  2
  •   Ram Prakash Bhat    11 年前

    这是Android 4到4.4(KITKAT)中的一个已知bug,并在“& GT;4.4”中得到解决。

    请参见这里: https://code.google.com/p/android/issues/detail?id=71936

        12
  •  2
  •   Charuක    9 年前

    即使我在XMPP通知应用程序中遇到了同样的问题,接收器消息也需要重新添加到列表视图中(使用 ArrayList )当我试图添加接收器内容通过 MessageListener (单独线程),应用程序退出上述错误。我通过添加内容来解决这个问题。 arraylist 和; setListviewadapater 通过 runOnUiThread method which is part of Activity class. This solved my problem.

        13
  •  1
  •   Leonardo Costa    10 年前

    I faced a similar problem, here's how I solved in my case. 我验证是否 task 已经是 RUNNING FINISHED because an task can run only once. Below you will see a partial and adapted code from my solution.

    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);
        }
    
    }
    
    }
    
        14
  •  1
  •   Charuක    9 年前

    我也有同样的问题,我解决了它。我的问题是我用了 listview performFiltering 我处理的是拥有数据的数组,这是个问题,因为这个方法没有在UI线程上运行,最终会引发一些问题。

        15
  •  1
  •   Charuක    9 年前

    One cause for this crash is that ArrayList object cannot change completely. 所以,当我移除一个项目时,我必须这样做:

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

    This fixed the crash for me.

        16
  •  1
  •   Charuක    9 年前

    在我的情况下,我称之为方法。 GetFilter() on an adapter from the TextWatcher() 方法的主要活动,并添加了一个for循环的数据 GETFILTER() . The solution was change the For loop to AfterTextChanged() sub method on main Activity and delete the call to GETFILTER()

        17
  •  0
  •   CHarris    10 年前

    I was also getting exact same error and using 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(); 在我的UI线程的底部,这是我的AsyncTask OnPostExecute方法。这样地:

     protected void onPostExecute(Void aVoid) {
    
     all my other stuff etc...
        all my other stuff etc...
    
               adapter.notifyDataSetChanged();
    
                    }
    
                });
            }
    

    现在我的应用程序工作。

    EDIT : In fact, my app still crashed about every 1 in 10 times, giving the same error.

    Eventually I came across runOnUiThread 在以前的一篇文章中,我认为这是有用的。所以我把它放在我的doinbackground方法中,就像这样:

    @Override
    protected Void doInBackground(Void... voids) {
    
        runOnUiThread(new Runnable() {
                          public void run() { etc... etc...
    

    我移除了 adapter.notifyDataSetChanged(); 方法。现在,我的应用程序永远不会崩溃。

        18
  •  0
  •   Charuක    9 年前

    请尝试下列解决方案之一:

    1. 有时,如果将新对象添加到线程中的数据列表中(或 doInBackground 方法),将发生此错误。解决方案是:创建一个临时列表,并在线程中向该列表添加数据(或 背景背景 然后,将所有数据从临时列表复制到UI线程中的适配器列表中(或 onPostExcute )

    2. 确保在UI线程中调用所有UI更新。

        19
  •  0
  •   Charuක    9 年前

             adapter.notifyDataSetChanged();
    

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

    hope it helps you

        20
  •  0
  •   Charuක    9 年前

    就像@mullins说的那样”
    我都加了东西然后打电话给 notifyDataSetChanged() 在UI线程中,我解决了这个问题。“穆林斯”。

    在我的情况下,我有 asynctask 我叫 doInBackground() method and the problem is solved, when I called from onPostExecute() I received the exception.

        21
  •  0
  •   Charuක    9 年前

    我有一个习俗 ListAdapter 呼唤着 super.notifyDataSetChanged() at the beginning and not the end of the method

    @Override
    public void notifyDataSetChanged() {
        recalculate();
        super.notifyDataSetChanged();
    }
    
        22
  •  0
  •   Samir    9 年前

    我有同样的情况,我有许多buttongroup把我的项目放在listview上,我在我的项目中改变了一些布尔值,比如holder.rbvar.setonclik…

    发生我的问题是因为我在getView()内调用了一个方法;并且在sharePreference内保存了一个对象,所以上面有相同的错误

    How I solved it; I removed my method inside getView() to notifyDataSetInvalidated() and problem gone

       @Override
        public void notifyDataSetChanged() {
            saveCurrentTalebeOnShare(currentTalebe);
            super.notifyDataSetChanged();
        }
    
        23
  •  0
  •   sreejith    8 年前

    i had the same problem. finally i got the solution

    在更新ListView之前,如果存在软键盘,请先关闭它。然后设置数据源并调用notifyDataSetChanged()。

    当在内部关闭键盘时,listview将更新其用户界面。it keep calling till closing keypad. 这一次,如果数据源发生更改,将引发此异常。 if data is updating in onActivityResult, there is a chance for same error.

     InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
    
            view.postDelayed(new Runnable() {
                @Override
                public void run() {
                    refreshList();
                }
            },100L);
    
        24
  •  0
  •   Homayoon Ahmadi    8 年前

    我的解决方案:

    1)创建一个 temp ArrayList .

    2) do your heavy works (sqlite row fetch , ...) in doInBackground method and add items to the temp arraylist.

    3) add all items from temp araylist to your listview's arraylist in onPostExecute 方法。

    note: 您可能希望从ListView中删除一些项,也可能从sqlite数据库中删除一些项,或者从SD卡中删除一些与项相关的文件,只需从数据库中删除项并删除它们的相关文件,然后将它们添加到临时数组列表中 background thread . 然后在 UI thread delete items existing in temp arraylist from the listview's arraylist.

    希望这有帮助。