我想做什么:运行后台线程,计算ListView内容并部分更新ListView,同时计算结果。
我知道我必须避免:我无法从后台线程中删除ListAdapter内容,因此我继承了AsyncTask并从onProgressUpdate发布结果(向适配器添加条目)。我的适配器使用结果对象的ArrayList,这些arraylists上的所有操作都是同步的。
其他人的研究:有非常有价值的数据here。对于500名用户来说,我几乎每天都会遇到崩溃,当我在onProgressUpdate中添加list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)
块时,崩溃率降低了10倍但没有消失。 (在answer)
我有时会得到什么:请注意,它很少发生(每周一次为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
,以避免闪烁和突然更改列表。它不能这样做,总是在基本列表更改时通知适配器。这个错误现在已经消失了。
答案 0 :(得分:114)
我遇到了同样的问题。
我在UI线程之外的ArrayList
添加了项目。
解决方案:我在UI线程中同时完成了adding the items
和notifyDataSetChanged()
调用。
答案 1 :(得分:26)
我有同样的问题,但我使用方法
修复了它requestLayout();
来自班级ListView
答案 2 :(得分:20)
这是 MultiThreading 问题并正确使用同步块可以防止这种情况发生。 不在UI线程上添加额外的东西,导致app的响应能力丧失。
我也面临同样的问题。而且最常见的答案表明,从UI Thread更改适配器数据可以解决问题。这将是有效的,但是一个快速简单的解决方案,但不是最好的解决方案。
正如你可以看到正常情况。从后台线程更新数据适配器并在UI线程中调用notifyDataSetChanged。
当ui线程正在更新视图而另一个后台线程再次更改数据时,会出现此illegalStateException。那一刻导致了这个问题。
因此,如果您将同步所有正在更改适配器数据的代码并进行notifydatasetchange调用。这个问题应该消失了。对我来说,我仍然在后台线程中更新数据。
这是我的案例特定代码供其他人参考。
主屏幕上的我的加载程序会将电话簿联系人加载到后台的数据源中。
@Override
public Void loadInBackground() {
Log.v(TAG, "Init loadings contacts");
synchronized (SingleTonProvider.getInstance()) {
PhoneBookManager.preparePhoneBookContacts(getContext());
}
}
此PhoneBookManager.getPhoneBookContacts从电话簿中读取联系人并将其填入哈希映射。哪个可以直接用于列表适配器绘制列表。
我的屏幕上有一个按钮。这将打开列出这些电话号码的活动。 如果我在上一个线程完成其工作之前直接在列表上设置了Adapter,那么快速导航的情况就会发生。它弹出了例外。这个问题的标题是什么。所以我必须在第二项活动中做这样的事情。
我在第二个活动中的加载程序等待第一个线程完成。直到它显示进度条。检查两个加载器的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());
}
}
}
希望这有帮助
答案 3 :(得分:15)
我通过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();
}
});
}
答案 4 :(得分:7)
我编写了这段代码并让它在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);
}
}
答案 5 :(得分:3)
几天前我遇到了同样的问题并导致每天数千次崩溃,大约0.1%的用户遇到这种情况。我尝试过setVisibility(GONE/VISIBLE)
和requestLayout()
,但崩溃次数只会减少一点。
我终于解决了它。 setVisibility(GONE/VISIBLE)
没有任何内容。 requestLayout()
没有任何内容。
最后我发现原因是我在更新数据后使用Handler
来调用notifyDataSetChanged()
,这可能导致某种情况:
checkForTap()
/ onTouchEvent()
,最后调用layoutChildren()
)notifyDataSetChanged()
并更新视图我又犯了一个错误:getCount()
,getItem()
和getView()
,我直接使用DataSource中的字段,而不是将它们复制到适配器。所以最后它崩溃了:
getCount()
和getView()
,listview查找数据不一致,并抛出java.lang.IllegalStateException: The content of the adapter has changed but...
之类的异常。如果您在IndexOutOfBoundException
中使用页眉/页脚,则另一个常见的例外是ListView
。所以解决方案很简单,当我的Handler触发适配器获取数据并调用notifyDataSetChanged()
时,我只是从我的DataSource将数据复制到适配器。崩溃现在再也不会发生了。
答案 6 :(得分:3)
如果Feed对象有一个List。
它是从非UI线程追加和截断的。
它适用于下面的适配器。
无论如何我在UI线程中调用FeedAdapter.notifyDataSetChanged
但稍后。
我喜欢这样,因为我的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;
}
}
答案 7 :(得分:3)
我的问题与 Filter 和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()
的调用之间,在UI线程中,一些其他UI线程代码可以执行,更改适配器的内容。
实际修复很简单,只需在请求执行过滤之前调用notifyDataSetChanged()
:
public void updateUnderlyingContacts(List<Contact> newContacts, String filter)
{
this.allContacts = newContacts;
this.filteredContacts = newContacts;
notifyDataSetChanged(); // Fix
getFilter().filter(filter);
}
答案 8 :(得分: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',我在getAdapter调用中返回WrappedAdapter(用于访问底层对象)。由于额外的“加载更多”项和抛出异常,这导致计数不同。
我之所以这样做是因为文档似乎可以做到
ListView.getAdapter javadoc
返回此ListView中当前使用的适配器。归来了 适配器可能与传递给的适配器不同 setAdapter(ListAdapter)但可能是WrapperListAdapter。
答案 9 :(得分:2)
即使我在XMPP通知应用程序中遇到同样的问题,也需要将接收者消息添加回列表视图(使用ArrayList
实现)。当我尝试通过MessageListener
(单独的线程)添加接收者内容时,应用程序退出时出现上述错误。我通过将内容添加到arraylist
&amp; setListviewadapater
到runOnUiThread
方法,它是Activity类的一部分。这解决了我的问题。
答案 10 :(得分:2)
这是Android 4到4.4(KitKat)中的已知错误,已在“&gt; 4.4”中解决
见这里:https://code.google.com/p/android/issues/detail?id=71936
答案 11 :(得分:2)
我遇到了完全相同的错误日志同样的问题。
在我的情况下,AsyncTask的onProgress()
使用mAdapter.add(newEntry)
将值添加到适配器。为了避免用户界面响应变慢,我设置了mAdapter.setNotifyOnChange(false)
并在第二次调用mAdapter.notifyDataSetChanged()
4次。每秒一次,数组被排序。
这项工作做得很好,看起来很容易上瘾,但遗憾的是,通过触摸显示的列表项目可能会使其崩溃。
但似乎我找到了一个可接受的解决方法。
我的猜测即使你只是在ui线程上工作,适配器也不会接受对它的数据的多次更改而不调用notifyDataSetChanged()
,因此我创建了一个存储所有新项目的队列,直到提到的300ms为止。过度。如果达到这个时刻,我会一次性添加所有存储的项目并致电notifyDataSetChanged()
。
到现在为止,我无法再崩溃。
答案 12 :(得分:1)
在我的情况下,我在主Activity上的GetFilter()
方法上调用了适配器上的方法TextWatcher()
,并在GetFilter()
上添加了For循环数据。
解决方案是在主Activity上将For循环更改为AfterTextChanged()
sub方法,并删除对GetFilter()
的调用
答案 13 :(得分:1)
此崩溃的一个原因是ArrayList
对象无法完全更改。
所以,当我删除一个项目时,我必须这样做:
mList.clear();
mList.addAll(newDataList);
这解决了我的崩溃。
答案 14 :(得分: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);
}
}
}
答案 15 :(得分:1)
我有同样的问题,我解决了。我的问题是我使用的是listview
,带有数组适配器和过滤器。在方法performFiltering
上我弄乱了有数据的数组,这是问题,因为这个方法没有在UI线程上运行,并且偶然会引起一些问题。
答案 16 :(得分:0)
请尝试以下解决方案之一:
有时,如果将新对象添加到线程(或doInBackground
方法)中的数据列表中,则会发生此错误。解决方案是:创建一个临时列表并在线程(或doInBackground
)中将数据添加到此列表,然后将所有数据从临时列表复制到UI线程中的适配器列表(或onPostExcute
)
确保在UI线程中调用所有UI更新。
答案 17 :(得分:0)
我在延迟图像加载器中添加新数据时遇到同样的问题 我只是把
adapter.notifyDataSetChanged();
in
protected void onPostExecute(Void args) {
adapter.notifyDataSetChanged();
// Close the progressdialog
mProgressDialog.dismiss();
}
希望它可以帮到你
答案 18 :(得分:0)
就像@Mullins所说&#34;
我在UI线程中添加了项目并调用了notifyDataSetChanged()
,我解决了这个问题。 - Mullins&#34;。
在我的情况下,我有asynctask
我在notifyDataSetChanged()
方法中调用了doInBackground()
,问题解决了,当我从onPostExecute()
来电时,我收到了异常。< / p>
答案 19 :(得分:0)
我有一个自定义ListAdapter
并且在方法的开头而不是方法结束时调用super.notifyDataSetChanged()
@Override
public void notifyDataSetChanged() {
recalculate();
super.notifyDataSetChanged();
}
答案 20 :(得分: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();
放在我的UI线程的底部来解决它,这是我的AsyncTask onPostExecute方法。像这样:
protected void onPostExecute(Void aVoid) {
all my other stuff etc...
all my other stuff etc...
adapter.notifyDataSetChanged();
}
});
}
现在我的应用程序正常运作。
编辑:事实上,我的应用程序仍然每10次崩溃一次,造成同样的错误。
最后我在上一篇文章中遇到了runOnUiThread
,我觉得这可能很有用。所以我将它放在我的doInBackground方法中,如下所示:
@Override
protected Void doInBackground(Void... voids) {
runOnUiThread(new Runnable() {
public void run() { etc... etc...
我删除了adapter.notifyDataSetChanged();
方法。现在,我的应用程序永远不会崩溃。
答案 21 :(得分:0)
我有同样的情绪,我有许多buttongroup在listview上我的项目,我正在改变我的项目中的一些布尔值,如holder.rbVar.setOnclik ......
我的问题发生了,因为我在getView()中调用了一个方法;并将一个对象保存在sharepreference中,所以我上面有同样的错误
我是如何解决的;我将getView()中的方法移除到notifyDataSetInvalidated()并且问题消失了
@Override
public void notifyDataSetChanged() {
saveCurrentTalebeOnShare(currentTalebe);
super.notifyDataSetChanged();
}
答案 22 :(得分:0)
在更新列表视图之前,如果软键盘存在,请先关闭它。之后设置数据源并调用notifydatasetchanged()。
在内部关闭键盘时listview将更新其ui。它一直打电话直到关闭键盘。那个时候如果数据源改变它会抛出这个异常。 如果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);
答案 23 :(得分:0)
我的解决方案:
1)创建temp ArrayList
。
2)在doInBackground
方法中执行繁重的工作(sqlite row fetch,...)并将项添加到temp arraylist。
3)使用onPostExecute
方法将临时araylist中的所有项目添加到listview的arraylist中。
note:
你可能想要从listview中删除一些项目,也可以从sqlite数据库中删除,也许从sdcard删除一些与项目相关的文件,只需从数据库中删除项目并删除它们的相关文件并将它们添加到temp arraylist在background thread
。然后在UI thread
中从listview的arraylist中删除临时arraylist中存在的项目。
希望这有帮助。
答案 24 :(得分:0)
adapter.notifyDataSetChanged()