在Android数组适配器中使用notifyDataSetChanged出现错误

18
11-06 19:52:25.958: E/AndroidRuntime(29609): java.lang.IllegalStateException: 适配器的内容已更改,但ListView未收到通知。确保您的适配器的内容不是在后台线程中修改的,而只能在UI线程中修改。[在ListView(-1,类 android.widget.ListPopupWindow $ DropDownListView)中使用Adapter(class com.example.parkfoxxlight_android.PlacesAutoCompleteAdapter)] 完整日志:http://pastebin.com/Hx7k28Rm 适配器的完整代码:http://pastebin.com/TfH1bXE3我正在使用https://developers.google.com/places/training/autocomplete-android中的示例,它具有相当多的默认代码,因此似乎存在Google代码中的错误? 应用程序仅在出现以上错误消息时才会崩溃。
protected void publishResults(CharSequence constraint,
        FilterResults results) {

    if (results != null && results.count > 0) {
        notifyDataSetChanged();
    } else {
        notifyDataSetInvalidated();
    }
}

活动 http://pastebin.com/FYzYtvXY

public class CityActivity extends Activity{

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.city);

            AutoCompleteTextView autoCompView = (AutoCompleteTextView) findViewById(R.id.autocomplete_city);

            PlacesAutoCompleteAdapter ad = new PlacesAutoCompleteAdapter(this);
            ProgressBar b = (ProgressBar)findViewById(R.id.progressBar1);
            ad.setLoadingIndicator(b);

            autoCompView.setAdapter(ad);
        }
}

有什么想法可以修复这个问题吗?我使用的是Android 4.3。


它在哪一行代码崩溃了?顺便说一下,你正在将Context转换为Activity,这是不安全的,并且在某些情况下肯定会导致崩溃。我认为你可以用mHandler.post替换act.runOnUiThread,并在构造函数中创建你的Handler。 - Tenfour04
这里是日志:http://pastebin.com/Hx7k28Rm - DarkLeafyGreen
@Tenfour04 活动和上下文,不是一样的吗? - DarkLeafyGreen
否则你就不需要进行强制类型转换。Activity是Context的子类,因此如果有人尝试使用new PlacesAutoCompleteAdapter(getBaseContext())来实例化您的适配器,当过滤器运行时,他们将会得到一个ClassCastException异常。 - Tenfour04
FilterperformFiltering() 方法在后台线程上运行,从该方法中,您正在更改适配器所基于的 resultList。如果您更改了该列表,并且在此期间 ListView 访问了适配器,则它将看到某些内容已更改,但并不知道这一点(它将不会很高兴)。请检查此代码片段 https://gist.github.com/luksprog/7382576。 - user
@Luksprog,根据您的评论,我重构了我的代码,问题消失了。您可以将此发布为一个问题。 - DarkLeafyGreen
2个回答

37
< p > FilterperformFiltering() 方法在后台线程上运行,从该方法中您正在更改适配器所基于的 resultList。如果您在那段时间内更改了该数据列表,并且此时 ListView 访问了适配器,则它会发现某些内容已经发生了变化,但是它并不知道这个变化(它会感到不满意)。您应该避免在 performFiltering 方法中使用 resultList,而应该简单地创建一个新的临时列表:

// in the performFiltering method which runs on a background thread:
@Override
protected FilterResults performFiltering(CharSequence constraint) {
     FilterResults filterResults = new FilterResults();
     ArrayList<String> queryResults;
     if (constraint != null && constraint.length() > 0) {
         queryResults = autocomplete(constraint);
     } else {
         queryResults = new ArrayList<String>(); // empty list/no suggestions showing if there's no valid constraint
     }
     filterResults.values = queryResults;
     filterResults.count = queryResults.size();
     return filterResults; // ## Heading ##
}

private List<String> autocomplete(String input) {
   // don't use the here the resultList List on which the adapter is based!
   // some custom code to get items from http connection
     ArrayList<String> queryResults = new ArrayList<String>(); // new list
     queryResults.add("Some String");
     return queryResults;
}

@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
     // update the data with the new set of suggestions
     resultList = (ArrayList<String>)results.values;
     if (results.count > 0) {
         notifyDataSetChanged();
     } else {
         notifyDataSetInvalidated();
     }
}

2
非常棒的伙计们!非常非常非常非常非常非常非常非常非常非常有帮助。令人难以置信...... - nAkhmedov
@Luksprog.. 很抱歉打扰并不想劫持这个帖子,但您能看一下这篇帖子吗?它基于您在这里的解决方案:http://stackoverflow.com/questions/28568337/autocompletetextview-illegalstateexception-the-content-of-the-adapter-has-chang - irobotxx
当constraint等于null时,行if (constraint != null || constraint.length() == 0) {会导致NPE。if块应该仅检查constraint!=null。 - Purushothaman Ramraj
1
@luksprog 短路是罪魁祸首。当约束为null时,constraint != null变为false,然后或运算符将尝试评估constraint.length()==0,从而导致NPE。我们需要的是if (constraint != null && constraint.length() > 0)。现在短路将保护免受NPE的影响。 - Purushothaman Ramraj
@PurushothamanRamraj 是的,我现在看到了,在那个时候我愚蠢地混淆了两个约束条件(不为空和不为空字符串)。很好的发现,我想知道为什么这么长时间以来没有人注意到并评论。 - user
我遇到了同样的问题,有人可以看一下我的问题吗? - Moeez

0

试试这个(只是猜测):

@Override
    public Filter getFilter() {
        Filter filter = new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults filterResults = new FilterResults();
                if (constraint != null) {
                    ArrayList list = autocomplete(constraint.toString());
                    if (list != null) {
                        filterResults.values = list;
                        filterResults.count = list.size();
                    }
                }
                return filterResults;
            }

            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                if (results != null && results.count > 0) {
                    //change the underlying data immediately before notifying UI                        
                    resultList = (ArrayList)results.values; 
                    notifyDataSetChanged();
                }
                else {
                    notifyDataSetInvalidated();
                }
            }};
        return filter;
    }

结果值必须转换为 (ArrayList<String>)? 已尝试,但自动完成不再起作用。 - DarkLeafyGreen
Filter.publishResults会自动在UI线程上执行。我真的无法想象为什么会出现这个错误。 - DarkLeafyGreen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接