197

我想要做的:运行一个后台线程来计算 ListView 内容并部分更新 ListView,同时计算结果。

我知道我必须避免:我不能从后台线程中弄乱 ListAdapter 的内容,所以我继承了 AsyncTask 并从 onProgressUpdate 发布结果(向适配器添加条目)。我的适配器使用结果对象的 ArrayList,这些数组列表上的所有操作都是同步的。

别人的研究:这里有非常有价值的数据。对于大约 500 名用户,我几乎每天都会遇到崩溃,当我list.setVisibility(GONE)/trackList.setVisibility(VISIBLE)在 onProgressUpdate 中添加块时,崩溃降低了 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)
[...]

帮助?不再需要了,见下文

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

4

25 回答 25

122

我遇到过同样的问题。

我正在将项目添加到ArrayListUI 线程之外。

解决方案:我都做了,并在 UI 线程中adding the items调用。notifyDataSetChanged()

于 2011-06-15T12:55:31.400 回答
27

我有同样的问题,但我使用方法修复了它

requestLayout();

从课堂上ListView

于 2011-09-01T16:27:16.010 回答
23

这是一个多线程问题,使用正确同步的块这是可以避免的。无需在 UI Thread 上添加额外的东西并导致应用程序失去响应能力。

我也面临同样的情况。正如最被接受的答案所暗示的那样,从 UI Thread 更改适配器数据可以解决这个问题。这将起作用,但它是一种快速简便的解决方案,但不是最好的解决方案。

正如您在正常情况下看到的那样。从后台线程更新数据适配器并在 UI 线程中调用 notifyDataSetChanged 有效。

当 ui 线程正在更新视图并且另一个后台线程再次更改数据时,会出现此非法状态异常。那一刻导致了这个问题。

因此,如果您将同步所有更改适配器数据并进行 notifydatasetchange 调用的代码。这个问题应该没了。对我来说已经过去了,我仍在从后台线程更新数据。

这是我的案例特定代码供其他人参考。

我在主屏幕上的加载器在后台将电话簿联系人加载到我的数据源中。

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

这个 PhoneBookManager.getPhoneBookContacts 从电话簿中读取联系人并将它们填充到哈希图中。可直接用于列表适配器绘制列表。

我的屏幕上有一个按钮。这将打开一个列出这些电话号码的活动。如果我在前一个线程完成其工作之前直接在列表上设置适配器,那么快速导航情况发生的频率会降低。它弹出异常。这是这个 SO 问题的标题。所以我必须在第二个活动中做这样的事情。

我在第二个活动中的加载器等待第一个线程完成。直到它显示一个进度条。检查两个加载器的 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());    
        }
    }

}

希望这可以帮助

于 2014-01-02T07:24:57.327 回答
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();
        }
    });
}
于 2013-11-13T02:33:56.497 回答
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);
    }
}
于 2010-06-29T20:10:08.437 回答
5

几天前我遇到了同样的问题,每天导致数千次崩溃,大约 0.1% 的用户遇到这种情况。我试过setVisibility(GONE/VISIBLE)and requestLayout(),但崩溃次数只减少了一点。

我终于解决了。什么都没有setVisibility(GONE/VISIBLE)。什么都没有requestLayout()

最后我发现原因是我在更新数据后使用了一个Handler调用notifyDataSetChanged(),这可能会导致一种:

  1. 将数据更新到模型对象(我称之为数据源)
  2. 用户触摸列表视图(可能会调用checkForTap()/onTouchEvent()最后调用layoutChildren()
  3. Adapter 从模型对象中获取数据并调用notifyDataSetChanged()和更新视图

而且我犯了另一个错误,在getCount(),getItem()和中getView(),我直接使用了 DataSource 中的字段,而不是将它们复制到适配器。所以最后它在以下情况下崩溃:

  1. 适配器更新最后一个响应给出的数据
  2. 当下一个响应返回时,DataSource 更新数据,这会导致项目计数发生变化
  3. 用户触摸列表视图,可能是点击或移动或翻转
  4. getCount()andgetView()被调用,listview 发现数据不一致,抛出类似java.lang.IllegalStateException: The content of the adapter has changed but.... 另一个常见的例外是IndexOutOfBoundException如果您在ListView.

所以解决方案很简单,当我的 Handler 触发适配器获取数据并调用时,我只需将数据从我的 DataSource 复制到适配器notifyDataSetChanged()。现在崩溃再也不会发生了。

于 2017-03-11T09:34:01.550 回答
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。

于 2014-03-09T17:34:46.127 回答
3

我的问题与将过滤器与 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);
}
于 2014-04-03T11:41:30.363 回答
3

如果 Feed 对象,我有一个列表。它是从非 UI 线程追加和截断的。它适用于下面的适配器。无论如何,我都会调用FeedAdapter.notifyDataSetChangedUI 线程,但稍后会调用。我之所以喜欢这样,是因为即使 UI 已死,我的 Feed 对象也会保留在本地服务的内存中。

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;
    }
}
于 2014-11-24T14:02:28.593 回答
2

即使我在 XMPP 通知应用程序中遇到了同样的问题,也需要将接收者消息添加回列表视图(使用 实现ArrayList)。当我尝试通过MessageListener(单独的线程)添加接收器内容时,应用程序退出并出现上述错误。我通过将内容添加到我的arraylist& setListviewadapaterthroughrunOnUiThread方法来解决这个问题,该方法是 Activity 类的一部分。这解决了我的问题。

于 2011-02-14T10:06:35.120 回答
2

我在完全相同的错误日志中面临同样的问题。在我onProgress()的 AsyncTask 的情况下,使用mAdapter.add(newEntry). 为了避免 UI 变得反应迟钝,我设置mAdapter.setNotifyOnChange(false)并每秒调用mAdapter.notifyDataSetChanged()4 次。每秒对数组进行一次排序。

这很好用,看起来很容易上瘾,但不幸的是,经常触摸显示的列表项可能会使它崩溃。

但似乎我找到了一个可以接受的解决方法。 我猜即使您只是在 ui 线程上工作,适配器也不会在不调用的情况下接受对其数据的许多更改,因此notifyDataSetChanged()我创建了一个队列来存储所有新项目,直到上述 300 毫秒结束。如果到了这一刻,我会一次性添加所有存储的项目并调用notifyDataSetChanged(). 直到现在我无法再让列表崩溃了

于 2011-04-18T06:28:50.113 回答
2

这是 Android 4 到 4.4(KitKat) 中的一个已知错误,已在“>4.4”中解决

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

于 2015-05-15T11:58:51.083 回答
1

我有同样的问题,我解决了。我的问题是我使用的是listview带有数组适配器和过滤器的 , 。在该方法上,performFiltering我弄乱了具有数据的数组,这是问题所在,因为该方法未在 UI 线程上运行,并且最终会引发一些问题。

于 2011-03-09T02:01:46.053 回答
1

此崩溃的一个原因是ArrayList对象无法完全更改。所以,当我删除一个项目时,我必须这样做:

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

这为我解决了崩溃问题。

于 2013-03-27T21:04:24.247 回答
1

在我的例子中,我从主 Activity 上的方法调用了GetFilter()适配器上的TextWatcher()方法,并使用 For 循环添加了数据GetFilter()。解决方案是将 For 循环更改AfterTextChanged()为主 Activity 上的子方法并删除对GetFilter()

于 2013-04-30T08:22:19.230 回答
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);
    }

}

}
于 2016-06-08T21:09:27.203 回答
1
adapter.notifyDataSetChanged()
于 2019-09-04T08:35:55.317 回答
0

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

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

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

于 2013-05-22T15:32:15.297 回答
0

在我刚刚放入的惰性图像加载器中添加新数据时我遇到了同样的问题

         adapter.notifyDataSetChanged();

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

希望对你有帮助

于 2014-12-11T15:31:39.243 回答
0

就像@Mullins 说的“
我都添加了项目并notifyDataSetChanged()在 UI 线程中调用,我解决了这个问题。——Mullins”。

在我的情况下,我有asynctask并且我调用notifyDataSetChanged()了该doInBackground()方法并且问题得到了解决,当我从onPostExecute()我收到异常时调用。

于 2015-04-15T04:00:25.880 回答
0

我有一个习惯,在方法的开头而不是结尾ListAdapter调用super.notifyDataSetChanged()

@Override
public void notifyDataSetChanged() {
    recalculate();
    super.notifyDataSetChanged();
}
于 2015-11-09T16:18:54.020 回答
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();方法。现在,我的应用程序永远不会崩溃。

于 2016-05-08T22:14:04.353 回答
0

我遇到了同样的情况,我在 listview 上的项目中有很多按钮组,并且我正在更改项目中的一些布尔值,例如 holder.rbVar.setOnclik ...

我的问题发生是因为我在 getView(); 中调用了一个方法;并在sharepreference中保存了一个对象,所以我在上面遇到了同样的错误

我是如何解决的;我在 getView() 中删除了我的方法以 notifyDataSetInvalidated() 并且问题消失了

   @Override
    public void notifyDataSetChanged() {
        saveCurrentTalebeOnShare(currentTalebe);
        super.notifyDataSetChanged();
    }
于 2017-02-11T19:48:13.923 回答
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);
于 2017-11-10T04:37:11.067 回答
0

我的解决方案:

1) 创建一个temp ArrayList.

2)在doInBackground方法中做繁重的工作(sqlite row fetch,...)并将项目添加到临时数组列表中。

3)将临时数组中的所有项目添加到方法中的列表视图的数组列表中onPostExecute

note:您可能想从 listview 中删除一些项目,并从 sqlite 数据库中删除,并可能从 sdcard 中删除与项目相关的一些文件,只需从数据库中删除项目并删除它们的相关文件并将它们添加到临时数组列表中background thread。然后UI thread从列表视图的数组列表中删除临时数组列表中存在的项目。

希望这可以帮助。

于 2017-12-16T02:16:08.907 回答