ListView IllegalStateException


贴出源代码:

android.widget.ListView

...

if(mItemCount == 0){
	resetList();
	invokeOnItemScrollListener();
	return;
}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 your adapter "
			+"is not modified from a background thread, but only from the UI thread. "
			+"Make sure your adapter calls notifyDataSetChanged() when its content changes. "
			+"[in ListView(" +getId() +"", + getClass + ") with Adapter(" + mAdapter.getClass+ ")]");
}

...

今天看见这个异常百思不得其解,幸好在论坛上看见一位牛人的解析,瞬间明了。

原文分析:

普通情况下。上述异常一般发生在我们启动一个后台线程载入数据,同一时候在主线程(即UI线程)刷新ListView在显示新载入的内容。

我们的做法通常是:在后台线程中把载入的数据放入到一个List中,而在主线程中实例化Adapter,这个Adapter中所用到的List正是在后台线程中载入的那个List。

发生上述异常的代码思路是这样子的。请看代码:

首先,我们定义一个List全局变量,后台线程中载入的数据就放到这个list中(请注意我标了红色的list变量,问题就出在它身上):

private List<Map<String,Object>>list= null;

接着,我们会启动一个后台线程,用于载入数据:

class GetDataThread implements Runnable{//单独启动一个线程用于载入歌曲列表

@Override
public void run() {
list= new ArrayList<Map<String,Object>>();

//然后把搜索出来的数据放入到list中。

}
}

最后,我们会在主线程中刷新界面。刷新界面的代码。我们是要放到handler中处理的:

class RefreshLocalMusicListThread implements Runnable{

@Override
public void run() {
local_lv = (ListView)findViewById(R.id.local_musiclist);

SimpleAdapter adapter = new SimpleAdapter(LocalActivity.this,list,R.layout.local_music_list,new String[] {"local_name","local_size"}, new int[]{R.id.local_name,R.id.local_size});
local_lv.setAdapter(adapter);
LocalActivity.this.registerForContextMenu(local_lv);
handler.postDelayed(refreshThread, 10);

}

}

以上的思路,是会发生上述异常的!

以下请看我的分析:

当执行 SimpleAdapter adapter = new SimpleAdapter(LocalActivity.this,list,R.layout.local_music_list,new String[] {"local_name","local_size"}, new int[]{R.id.local_name,R.id.local_size});时集合list中数据与我们的listView是绑定在一起的了。

此时。,假如list中的数据有5条,即list.size()==5,这时与listView绑定的就是5条数据。可是,我们的后台线程还在执行,list中的数据会发生变化,然而我们的listView认定的就是之前仅仅有5条数据的list,可是这时的list中的数据已经不是5条了。就是这个冲突导致了上述的异常!!!发生在else if()推断语句处

网上有这样一种解决方法(实际上解决不了问题):

在 adapter.notifyDataSetChanged() 之前调用listview.setVisibility(View.GONE);在adapter.notifyDataSetChanged() 之后调用listview.setVisibility(View.VISIBLE)

可是这是错误的!!


正确的解决方法是这种:

既然与listView绑定了的list发生了变化而没来得及通知listView导致了上述的异常,那我们就针对这一点,仅仅要listView与list绑定后,在listView显示之前不要让list发现变化即可了。做法有非常多种。我个人的做法是这样子的:

首先,定义一个独立的List:

private List<Map<String,Object>> data = null;

接着,在onCreate或者onResume中初始化它(当然,你也能够在每次用到它的时候初始化它。只是这样子会初始化非常多对象,浪费内存,不推荐):

data = new ArrayList<Map<String,Object>>();

然后,在创建adapter之前,把list中数据放入到集合data中。注意千万不要直接赋值:data = list(这是错误的。由于这样data也指向了list所在的内存地址,即data跟list是同一个对象。list改变的话data也跟着改变)。应该这么做:

data.clear();//要先清空data中的数据。避免把list中的数据反复放入data中。

data.addAll(list);//这样做。list中的数据就放入到data中,之后list在后台线程中改变,但data不会改变,这时,你再

SimpleAdapter adapter = new SimpleAdapter(LocalActivity.this,data,R.layout.local_music_list,new String[] {"local_name","local_size"}, new int[]{R.id.local_name,R.id.local_size});

listView与data绑定,就不会发生上述异常了!

总结来说。即创建一个缓存变量。存储的值是第一次查询得到数据 ,后台线程继续查询出来的数据不再使用。如此就保证了显示数据时不会报出异常。


优质内容筛选与推荐>>
1、[BZOJ 4977][Lydsy1708月赛]跳伞求生
2、如何在Windows Server2008 中管理本地用户和组?
3、(转发) 静态变量的安全问题
4、关于string与cstring的一些总结
5、Java多线程14:生产者/消费者模型


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号