Android,ListView IllegalStateException:"适配器的内容已更改,但ListView未收到通知"

tom*_*ash 187 android listview exception adapter

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

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

其他人的研究:有非常有价值的数据在这里.对于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)
[...]
Run Code Online (Sandbox Code Playgroud)

救命?不再需要了,见下文

最终答案:事实证明,我notifyDataSetChanged每次调用5次以避免闪烁和突然更改列表.它不能这样做,总是在基本列表更改时通知适配器.这个错误现在已经消失了.

Mul*_*ins 116

我遇到过同样的问题.

ArrayList在UI线程的外部添加了项目.

解决方案:我已经完成了两个,adding the itemsnotifyDataSetChanged()在UI线程中调用.

  • 亲切评论,真的. (23认同)
  • 我都添加了项目并在UI线程中调用了notifyDataSetChanged(),我解决了这个问题. (4认同)
  • @Mullins请问您能为您的解决方案展示样品吗?我遇到了同样的问题 (4认同)
  • 这是一个多线程问题并使用正确同步的块这可以防止.没有在UI线程上添加额外的东西并导致app的响应能力丧失.请在下面查看我的答案. (2认同)
  • 对于未来的读者 - 在后台线程中进行计算是一件好事,但是您应该将结果传递给UI线程,并将项目添加到适配器基本集合并在同一代码块中通知适配器. (2认同)

gia*_*200 27

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

requestLayout();
Run Code Online (Sandbox Code Playgroud)

来自全班 ListView

  • 什么时候应该调用这种方法? (35认同)
  • 在将项目添加到ListView或更改它的适配器之后@DeBuGGeR. (3认同)
  • 只是为了记录,@ Dr.aNdRO,你在AsyncTask收集你的结果.doInBackground()并保存结果以更新AsyncTask.onPostExecute()中的列表,该列表将在UI线程中运行.如果您*需要*随时更新,请使用AsyncTask.publishProgress()和AsyncTask.onProgressUpdate(),它也在UI线程中运行. (2认同)
  • 它对我有用,但有人可以解释为什么吗? (2认同)

Jav*_*tor 20

这是一个MultiThreading问题并使用正确同步的块这可以防止.不在UI线程上添加额外的东西,导致应用程序的响应能力丧失.

我也面临同样的问题.而且最常见的答案表明,从UI Thread更改适配器数据可以解决问题.这将是有效的,但是一个快速简单的解决方案,但不是最好的解决方案.

正如你可以看到正常情况.从后台线程更新数据适配器并在UI线程中调用notifyDataSetChanged.

当ui线程正在更新视图而另一个后台线程再次更改数据时,会出现此illegalStateException.那一刻导致了这个问题.

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

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

我的主屏幕上的加载程序将电话簿联系人加载到后台的数据源中.

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

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

}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助


tri*_*iad 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();
        }
    });
}
Run Code Online (Sandbox Code Playgroud)


Ric*_*ler 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);
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

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

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

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

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

我犯了另一个错误,在getCount(), getItem()and 中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()。崩溃现在再也不会发生了。