Android,ListView非法状态异常:"适配器的内容已更改,但ListView未收到通知

我想做的事:运行计算ListView内容的后台线程,并在计算结果的同时部分更新ListView.

我知道我必须避免的:我不能弄乱后台线程中的ListAdapter内容,所以我从OnProgressUpdate继承了AsyncTask和Publish Result(将条目添加到适配器)。我的适配器使用结果对象的ArrayList,这些ArrayList上的所有操作都是同步的。

他人的研究在这里有非常宝贵的数据。对于一组大约500个用户,我几乎每天都会遇到崩溃,当我在OnProgressUpdate中添加list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)块时,崩溃减少了10倍,但并没有消失。(在回答中提出)

有时我得到了什么:请注意,这种情况很少发生(3.5K用户中的一个每周一次)。但我想彻底摆脱这个错误。下面是部分堆栈跟踪:

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

帮助?不再需要,见下文

事实证明,最终答案:,我每插入5次就调用notifyDataSetChanged,以避免闪烁和突然的列表更改。它不能这样做,当基本列表更改时,总是通知适配器。这只虫子现在对我来说早就消失了。

135339 次浏览

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

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.

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.

I'm was facing the same problem with exactly the same error log. In my case onProgress() of the AsyncTask adds the values to the adapter using mAdapter.add(newEntry). To avoid the UI becoming less responsive I set mAdapter.setNotifyOnChange(false) and call mAdapter.notifyDataSetChanged() 4 times the second. Once per second the array is sorted.

This work well and looks very addictive, but unfortunately it is possible to crash it by touching the shown list items often enough.

But it seems I have found an acceptable workaround. My guess it that even if you just work on the ui thread the adapter does not accept many changes to it's data without calling notifyDataSetChanged(), because of this I created a queue that is storing all the new items until the mentioned 300ms are over. If this moment is reached I add all the stored items in one shot and call notifyDataSetChanged(). Until now I was not able to crash the list anymore.

I had the same issue.

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

Solution: I have done both, adding the items and called notifyDataSetChanged() in the UI thread.

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

requestLayout();

from the class ListView

One cause for this crash is that ArrayList object cannot change completely. So, when I remove an item, I have to do this:

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

This fixed the crash for me.

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

Please try one of these solutions :

  1. Sometimes, if you add new object to data list in a thread (or doInBackground method), this error will occur. The solution is : create a temporary list and do adding data to this list in thread(or doInBackground), then do copying all data from temporary list to the list of adapter in UI thread (or onPostExcute)

  2. Make sure all UI updates are called in UI thread.

I solved this by have 2 Lists. One list I use for only the adapter, and I do all data changes/updates on the other list. This allows me to do updates on one list in a background thread, and then update the "adapter" list in the main/UI thread:

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

This is a MultiThreading Issue and Using Properly Synchronized 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. If i directly setAdapter over the list before the previous thread finishes its work which is fast naviagtion case happens less often. It pops up the exception .Which is title of this SO question. So i have to do something like this in the second activity.

My loader in the second activity waits for first thread to complete. Till it shows a progress bar. Check the loadInBackground of both the loaders.

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


}

Hope this helps

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.

My issue was related to the use of 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);
}

Calling filter() in the last line will (and must) cause notifyDataSetChanged() to be called in the Filter's publishResults() method. This may work okay sometimes, specially in my fast Nexus 5. But in reality, it's hiding a bug that you will notice with slower devices or in resource intensive conditions.

The problem is that the filtering is done asynchronously, and thus between the end of the filter() statement and the call to publishResults(), both in the UI thread, some other UI thread code may execute and change the content of the adapter.

The actual fix is easy, just call notifyDataSetChanged() also before requesting the filtering to be performed:

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

I have a List if Feed objects. It's appended and truncated from none-UI thread. It works fine with adapter below. I call 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;
}
}

i haved same problem when add new data in lazy image loader i just put

         adapter.notifyDataSetChanged();

in

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

hope it helps you

Like @Mullins said "
I both added the items and called notifyDataSetChanged() in the UI thread and I resolved this. – Mullins".

In my case I have asynctask and I called notifyDataSetChanged() in the doInBackground() method and the problem is solved, when I called from onPostExecute() I received the exception.

This is a known bug in Android 4 to 4.4(KitKat) and is resolved in ">4.4"

See here: https://code.google.com/p/android/issues/detail?id=71936

I had a custom ListAdapter and was calling super.notifyDataSetChanged() at the beginning and not the end of the method

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

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

I solved it by puttingadapter.notifyDataSetChanged(); at the bottom of my UI thread, that is my AsyncTask onPostExecute method. Like this :

 protected void onPostExecute(Void aVoid) {


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


adapter.notifyDataSetChanged();


}


});
}

Now my app works.

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

Eventually I came across runOnUiThread on a previous post, which I thought could be useful. So I put it in my doInBackground method, like this :

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


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

And I removed the adapter.notifyDataSetChanged(); method. Now, my app never crashes.

I faced a similar problem, here's how I solved in my case. I verify if the task already is RUNNING or 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);
}


}


}

I had the same sittuation , I had many buttongroup insite my item on listview and I was changing some boolean values inside my item like holder.rbVar.setOnclik...

my problem occured because I was calling a method inside getView(); and was saving an object inside sharepreference, so I had same error above

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

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

Several days ago I met the same problem and causes several thousands of crash per day, about 0.1% of users meet this situation. I tried setVisibility(GONE/VISIBLE) and requestLayout(), but crash count only decreases a little.

And I finally solved it. Nothing with setVisibility(GONE/VISIBLE). Nothing with requestLayout().

Finally I found the reason is I used a Handler to call 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() and finally calls layoutChildren() )
  3. Adapter gets data from model object and call notifyDataSetChanged() and update views

And I made another mistake that in getCount(), getItem() and getView(), I directly use fields in DataSource, rather than copy them to the adapter. So finally it crashes when:

  1. Adapter updates data which last response gives
  2. When next response back, DataSource updates data, which causes item count change
  3. User touches listview, which may be a tap or a move or flip
  4. getCount() and getView() is called, and listview finds data is not consistent, and throws exceptions like java.lang.IllegalStateException: The content of the adapter has changed but.... Another common exception is an IndexOutOfBoundException if you use header/footer in ListView.

So solution is easy, I just copy data to adapter from my DataSource when my Handler triggers adapter to get data and calls notifyDataSetChanged(). The crash now never happens again.

i had the same problem. finally i got the solution

before updating listview, if the soft keypad is present close it first. after that set data source and call notifydatasetchanged().

while closing keypad internally listview will update its ui. it keep calling till closing keypad. that time if data source change it willl throw this exception. 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);

My solution:

1) create a 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 method.

note: you may want to delete some items from listview and also delete from sqlite database and maybe delete some files related to items from sdcard , just remove items from database and remove their related files and add them to temp arraylist in background thread. then in UI thread delete items existing in temp arraylist from the listview's arraylist.

Hope this helps.

adapter.notifyDataSetChanged()