Android,ListView IllegalStateException:“适配器的内容已更改,但 ListView 未收到通知”
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3132021/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android, ListView IllegalStateException: "The content of the adapter has changed but ListView did not receive a notification"
提问by tomash
What I want to do: run a background thread which calculates ListView contents and update ListView partially, while results are calculated.
我想要做的是:运行一个后台线程来计算 ListView 内容并部分更新 ListView,同时计算结果。
What I know I have to avoid: I cannot mess with ListAdapter contents from background thread, so I inherited AsyncTask and publish result (add entries to adapter) from onProgressUpdate. My Adapter uses ArrayList of result objects, all operations on those arraylists are synchronized.
我知道我必须避免:我不能从后台线程弄乱 ListAdapter 内容,所以我继承了 AsyncTask 并从 onProgressUpdate 发布结果(向适配器添加条目)。我的适配器使用结果对象的 ArrayList,对这些数组列表的所有操作都是同步的。
Research of other people: 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)
block in onProgressUpdate, crashes lowered by a factor of 10 but not disappeared. (it was suggested in answer)
其他人的研究:有非常有价值的数据在这里。对于大约 500 名用户,我几乎每天都遇到崩溃,当我list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)
在 onProgressUpdate 中添加块时,崩溃降低了 10 倍,但并没有消失。(这是在回答中提出的)
What I got sometimes: please notice, it happens really rarely (once a week for one of 3.5k users). But I'd like to get rid of this bug completely. Here is partial stacktrace:
我有时会得到什么:请注意,这种情况很少发生(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)
[...]
Help?Not needed anymore, see below
帮助?不再需要,见下文
FINAL ANSWER:As it turned out, I was calling notifyDataSetChanged
every 5 insertions to avoid flickering and sudden list changes. It cannot be done such way, always notify adapter when base list changes. This bug it long gone for me now.
最终答案:事实证明,我notifyDataSetChanged
每插入 5 次就调用一次,以避免闪烁和突然的列表更改。不能这样做,当基本列表更改时总是通知适配器。这个错误现在对我来说早就消失了。
采纳答案by Mullins
I had the same issue.
我遇到过同样的问题。
I was adding items to my ArrayList
outside the UI thread.
我ArrayList
在 UI 线程之外向我添加了项目。
Solution: I have done both, adding the items
and called notifyDataSetChanged()
in the UI thread.
解决方案:我都做了,adding the items
并notifyDataSetChanged()
在 UI 线程中调用。
回答by gian1200
I had the same problem, but I fixed it using the method
我遇到了同样的问题,但我使用该方法修复了它
requestLayout();
from the class ListView
从班级 ListView
回答by Javanator
This is a MultiThreadingIssue and Using Properly SynchronizedBlocks This can be prevented. Without putting extra things on UI Thread and causing loss of responsiveness of app.
这是一个多线程问题,使用正确同步的块可以防止这种情况。不会在 UI 线程上放置额外的东西并导致应用程序的响应能力下降。
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.
我也面临同样的情况。正如大多数人接受的答案所暗示的那样,从 UI Thread 更改适配器数据可以解决这个问题。这会起作用,但它是一种快速简便的解决方案,但不是最好的解决方案。
As you can see for a normal case. Updating data adapter from background thread and calling notifyDataSetChanged in UI thread works.
正如您在正常情况下所见。从后台线程更新数据适配器并在 UI 线程中调用 notifyDataSetChanged 工作。
This illegalStateException arises when a ui thread is updating the view and another background thread changes the data again. That moment causes this issue.
当一个 ui 线程正在更新视图而另一个后台线程再次更改数据时,就会出现这种非法状态异常。那一刻导致了这个问题。
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.
因此,如果您将同步所有更改适配器数据并进行 notifydatasetchange 调用的代码。这个问题应该没有了。对我来说已经过去了,我仍在更新后台线程中的数据。
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.
这个 PhoneBookManager.getPhoneBookContacts 从电话簿中读取联系人并将它们填充到哈希映射中。这可直接用于列表适配器绘制列表。
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.
我的屏幕上有一个按钮。这会打开一个列出这些电话号码的活动。如果我在前一个线程完成其工作之前直接在列表上设置适配器,这是快速导航情况发生的频率较低。它弹出异常。这是这个SO问题的标题。所以我必须在第二个活动中做这样的事情。
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.
我在第二个活动中的加载器等待第一个线程完成。直到它显示一个进度条。检查两个加载器的 loadInBackground。
Then it creates the adapter and deliver it to the activity where on ui thread i call setAdapter.
然后它创建适配器并将其传递给我在 ui 线程上调用 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
希望这可以帮助
回答by triad
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:
我通过有 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();
}
});
}
回答by Rich Schuler
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.
我编写了这段代码,让它在 2.1 模拟器映像中运行了大约 12 个小时,但没有收到 IllegalStateException。我要给 android 框架一个怀疑的好处,并说它很可能是你的代码中的一个错误。我希望这有帮助。也许您可以根据您的列表和数据调整它。
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);
}
}
回答by HJWAJ
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.
几天前我遇到了同样的问题,每天导致数千次崩溃,大约有 0.1% 的用户遇到这种情况。我试过setVisibility(GONE/VISIBLE)
and requestLayout()
,但崩溃次数只会减少一点。
And I finally solved it. Nothing with setVisibility(GONE/VISIBLE)
. Nothing with requestLayout()
.
我终于解决了它。什么都没有setVisibility(GONE/VISIBLE)
。什么都没有requestLayout()
。
Finally I found the reason is I used a Handler
to call notifyDataSetChanged()
after update data, which may lead to a sort of:
最后我发现原因是我在更新数据后使用了一个Handler
来调用notifyDataSetChanged()
,这可能会导致一种:
- Updates data to a model object(I call it a DataSource)
- User touches listview(which may call
checkForTap()
/onTouchEvent()
and finally callslayoutChildren()
) - Adapter gets data from model object and call
notifyDataSetChanged()
and update views
- 将数据更新到模型对象(我称之为数据源)
- 用户触摸列表视图(可能调用
checkForTap()
/onTouchEvent()
并最终调用layoutChildren()
) - 适配器从模型对象获取数据并调用
notifyDataSetChanged()
和更新视图
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:
我犯了另一个错误,在getCount()
, getItem()
and 中getView()
,我直接使用了 DataSource 中的字段,而不是将它们复制到适配器中。所以最终它在以下情况下崩溃:
- Adapter updates data which last response gives
- When next response back, DataSource updates data, which causes item count change
- User touches listview, which may be a tap or a move or flip
getCount()
andgetView()
is called, and listview finds data is not consistent, and throws exceptions likejava.lang.IllegalStateException: The content of the adapter has changed but...
. Another common exception is anIndexOutOfBoundException
if you use header/footer inListView
.
- 适配器更新上次响应给出的数据
- 当下一个响应返回时,DataSource 更新数据,这会导致项目计数发生变化
- 用户触摸列表视图,可能是轻按或移动或翻转
getCount()
andgetView()
被调用,listview 发现数据不一致,抛出java.lang.IllegalStateException: The content of the adapter has changed but...
. 另一个常见的例外是IndexOutOfBoundException
如果您在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.
所以解决方案很简单,当我的 Handler 触发适配器获取数据并调用时,我只需将数据从我的 DataSource 复制到适配器notifyDataSetChanged()
。崩溃现在再也不会发生了。
回答by aaronvargas
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.
经过大量调试后,这是我的一个错误,但也是 Android 代码中的不一致。
When the validation happens, this code is executed in ListView
当验证发生时,这段代码在 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)
但是当 onChange 发生时,它会在 AdapterView(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.
就我而言,由于它是一个“LoadMoreAdapter”,因此我在 getAdapter 调用中返回了 WrappedAdapter(用于访问底层对象)。由于额外的“加载更多”项目和抛出的异常,这导致计数不同。
I only did this because the docs make it seem like it's ok to do
我这样做只是因为文档使它看起来可以这样做
ListView.getAdapter javadoc
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.
返回当前在此 ListView 中使用的适配器。返回的适配器可能与传递给 setAdapter(ListAdapter) 的适配器不同,而可能是 WrapperListAdapter。
回答by cprcrack
My issue was related to the use of a Filtertogether with the ListView.
我的问题与将过滤器与 ListView 一起使用有关。
When setting or updating the underlying data model of the ListView, I was doing something like this:
在设置或更新 ListView 的底层数据模型时,我正在做这样的事情:
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.
filter()
在最后一行调用将(并且必须)导致notifyDataSetChanged()
在 Filter 的publishResults()
方法中调用。这有时可能会正常工作,特别是在我的快速 Nexus 5 中。但实际上,它隐藏了一个错误,您会在较慢的设备或资源密集型条件下注意到该错误。
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.
问题是过滤是异步完成的,因此在filter()
语句结束和调用 之间publishResults()
,两者都在 UI 线程中,一些其他 UI 线程代码可能会执行并更改适配器的内容。
The actual fix is easy, just call notifyDataSetChanged()
also before requesting the filtering to be performed:
实际的修复很简单,只需notifyDataSetChanged()
在请求执行过滤之前调用:
public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
this.allContacts = newContacts;
this.filteredContacts = newContacts;
notifyDataSetChanged(); // Fix
getFilter().filter(filter);
}
回答by ilya
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.
如果 Feed 对象,我有一个列表。它是从非 UI 线程中附加和截断的。它适用于下面的适配器。我FeedAdapter.notifyDataSetChanged
无论如何都会调用UI 线程,但稍后会调用。我喜欢这样做是因为我的 Feed 对象即使在 UI 死机时也保留在本地服务的内存中。
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;
}
}
回答by Lars K.
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.
我正面临同样的问题,错误日志完全相同。在我onProgress()
的 AsyncTask示例中,使用mAdapter.add(newEntry)
. 为了避免用户界面变得不那么敏感,我设置mAdapter.setNotifyOnChange(false)
并mAdapter.notifyDataSetChanged()
每秒调用4 次。每秒对数组进行一次排序。
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.
但似乎我找到了一个可以接受的解决方法。我的猜测是,即使您只是在 ui 线程上工作,适配器也不会在不调用的情况下接受对其数据的许多更改,因此notifyDataSetChanged()
我创建了一个队列,该队列存储所有新项目,直到提到的 300 毫秒结束。如果到达这一刻,我会一次性添加所有存储的项目并调用notifyDataSetChanged()
. 直到现在我无法再破坏列表。