• 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绑定,就不会发生上述异常了!

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


  • 相关阅读:
    .net core 3.1 使用Redis缓存
    JavaSE 高级 第11节 缓冲输入输出字节流
    JavaSE 高级 第10节 字节数组输出流ByteArrayOutputStream
    JavaSE 高级 第09节 字节数组输入流ByteArrayInputStream
    JavaSE 高级 第08节 文件输出流FileOutputStream
    JavaSE 高级 第07节 文件输入流FileInputStream
    JavaSE 高级 第06节 初识I、O流
    JavaSE 高级 第05节 日期类与格式化
    JavaSE 高级 第04节 StringBuffer类
    JavaSE 高级 第03节 Math类与猜数字游戏
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/5101781.html
Copyright © 2020-2023  润新知