Android AutoCompleteTextView在快速删除文本时崩溃

8

我有一份关键词列表(大约1000个单词),将它设置为ArrayAdapter,由AutoCompleteTextView处理。基本过程可以正常运作。问题出现在我选择了一个长单词(10个字符以上),然后使用键盘退格按钮来删除单词(按住按钮不放),当我删除了5个字符时,应用程序崩溃,并显示以下错误。

01-16 13:27:23.082: ERROR/AndroidRuntime(2874): FATAL EXCEPTION: main
        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(-1, class android.widget.AutoCompleteTextView$DropDownListView) with Adapter(class com.hdm_i.dm.corp.android.muenchen.adapters.PoiAutoCompleteAdapter)]
        at android.widget.ListView.layoutChildren(ListView.java:1527)
        at android.widget.AbsListView.onLayout(AbsListView.java:1430)
        at android.view.View.layout(View.java:7228)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:338)
        at android.view.View.layout(View.java:7228)
        at android.view.ViewRoot.performTraversals(ViewRoot.java:1145)
        at android.view.ViewRoot.handleMessage(ViewRoot.java:1865)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:130)
        at android.app.ActivityThread.main(ActivityThread.java:3687)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:507)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
        at dalvik.system.NativeStart.main(Native Method)  

以下是我的代码,我做错了什么吗?感谢您提供的建议:-)
public class PoiAutoCompleteAdapter extends ArrayAdapter<SearchTextAutoSuggest> implements Filterable {

    private List<SearchTextAutoSuggest> searchTextAutoSuggestList = new ArrayList<SearchTextAutoSuggest>();
    private SearchTextAutoSuggest defaultSuggestion = new SearchTextAutoSuggest();

    private Handler uiThreadHandler;

    public PoiAutoCompleteAdapter(Context context, int viewResourceId, Handler uiThreadHandler) {
        super(context, viewResourceId);
        this.uiThreadHandler = uiThreadHandler;
        defaultSuggestion.setName(AppConstants.DEFAULT_SEARCH_STRING_NAME);
    }

    @Override
    public int getCount() {
        return searchTextAutoSuggestList.size();
    }

    @Override
    public SearchTextAutoSuggest getItem(int position) {
        return searchTextAutoSuggestList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public Filter getFilter() {
        Filter filter = new Filter() {

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                FilterResults filterResults = new FilterResults();
                synchronized (filterResults) {
                    if (constraint != null) {
                        // Clear and Retrieve the autocomplete results.
                        searchTextAutoSuggestList.clear();
                        searchTextAutoSuggestList = getFilteredResults(constraint);

                        // Assign the data to the FilterResults
                        filterResults.values = searchTextAutoSuggestList;
                        filterResults.count = searchTextAutoSuggestList.size();
                    }

                    return filterResults;
                }
            }

            @Override
            protected void publishResults(CharSequence constraint, final FilterResults filterResults) {
                uiThreadHandler.post(new Runnable() {
                    public void run() {
                        synchronized (filterResults) {
                            if (filterResults != null && filterResults.count > 0) {
                                notifyDataSetChanged();
                            } else {
                                Logs.e("Tried to invalidate");
                                notifyDataSetInvalidated();
                            }
                        }
                    }
                });
            }
        };
        return filter;
    }

    private List<SearchTextAutoSuggest> getFilteredResults(CharSequence constraint) {
        List<SearchTextAutoSuggest> searchTextAutoSuggestList = AppContext.searchTextAutoSuggestList;
        List<SearchTextAutoSuggest> filteredSearchTextAutoSuggestList = new ArrayList<SearchTextAutoSuggest>();

        // Assign constraint as a default option into the list
        defaultSuggestion.setLabel(getContext().getString(R.string.general_default_search_str) + " \'" + constraint + "\'");
        filteredSearchTextAutoSuggestList.add(defaultSuggestion);

        for (int i = 0; i < searchTextAutoSuggestList.size(); i++) {
            if (searchTextAutoSuggestList.get(i).getLabel().toLowerCase().startsWith(constraint.toString().toLowerCase())) {
                filteredSearchTextAutoSuggestList.add(searchTextAutoSuggestList.get(i));
            }
        }

        return filteredSearchTextAutoSuggestList;
    }

}


为什么要扩展ArrayAdapter? - pskink
@pskink通常情况下,当我为列表/选择器创建适配器时,我会使用数组适配器来管理我的列表。基于我看到的一些示例...但是否有更好的方法来处理它? - Thilek
请看我在这里的答案:https://dev59.com/0mIj5IYBdhLWcg3wzIJo - pskink
@pskink,我注意到你使用了SimpleCursorAdapter,但它能用于对象列表吗?因为在我的情况下,当用户点击关键字时,我需要提供对象,以便我可以获取与该关键字附加的其他参数作为一个对象。 - Thilek
当然,您可以向返回的Cursor中添加任何内容,或者只使用所需的_id列来存储原始对象数组的索引。 - pskink
@pskink 谢谢。我会尝试的。 - Thilek
3个回答

10

因为performFiltering在工作线程中执行,你在该线程中分配了searchTextAutoSuggestList变量,但是你只能在UI线程中更改适配器的数据。另外publishResults方法在UI线程中执行,所以你不需要在这里使用任何处理程序。

@Override
protected FilterResults performFiltering(CharSequence constraint) {
    FilterResults filterResults = new FilterResults();
    synchronized (filterResults) {
        if (constraint != null) {
            // Clear and Retrieve the autocomplete results.
            List<SearchTextAutoSuggest> resultList = getFilteredResults(constraint);

            // Assign the data to the FilterResults
            filterResults.values = resultList;
            filterResults.count = resultList.size();
        }
        return filterResults;
    }
}

@Override
protected void publishResults(CharSequence constraint, final FilterResults filterResults) {
    if (filterResults != null && filterResults.count > 0) {
        searchTextAutoSuggestList.clear();
        searchTextAutoSuggestList = filterResults.values;
        notifyDataSetChanged();
    } else {
        Logs.e("Tried to invalidate");
        notifyDataSetInvalidated();
    }

}

2
谢谢回复,它起作用了。有趣的是,我的第一个实现是基于 Google 开发者网站上的示例。不知道其他用户是否遇到过像我这样的问题。https://developers.google.com/places/training/autocomplete-android - Thilek
2
嗨Thilek,我也遇到了同样的错误。问题在于我们正在更改'searchTextAutoSuggestList'引用。而不是searchTextAutoSuggestList = filterResults.values,请使用searchTextAutoSuggestList.addAll((List<SearchTextAutoSuggest>)filterResults.values),并且将searchTextAutoSuggestList实例与filterResults同步。 - Mael
@Mael 现在有意义了。谢谢 ;) - Thilek
在这两种情况下,我都调用了notifyDataSetChanged()而不是notifyDataSetInvalidated()。调用前者解决了我的问题。 - Ahmet Gokdayi

2

我曾经遇到同样的问题,在进行了大量调试和研究之后,通过重写 notifyDataSetChanged() 方法并评估 suggestionList 的大小来解决了问题。代码段如下所示。

private int size = 0;

     @Override
        public void notifyDataSetChanged() {
             size = suggestionList.size();
            super.notifyDataSetChanged();
        }

然后在 getCount() 中返回大小:

@Override
    public int getCount() {

        return size; // Return the size of the suggestions list.
    }

...过滤器代码放在这里:

 private class CustomFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            suggestions.clear();
            FilterResults filterResults = new FilterResults();
            try {

                    if (originalList != null && constraint != null) { // Check if the Original List and Constraint aren't null.
                        try {
                            for (int i = 0; i < originalList.size(); i++) {
                                // if (originalList.get(i).toLowerCase().contains(constraint)) {
                                if (originalList.get(i).toLowerCase().contains(constraint.toString().toLowerCase())) {
                                    suggestionList.add(originalList.get(i)); // If TRUE add item in Suggestions.
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    } else {
                        notifyDataSetChanged();
                    }

            } catch (Exception e) {
                e.printStackTrace();
            }
           // Create new Filter Results and return this to publishResults;
            filterResults.values = suggestionList;
            filterResults.count = suggestionList.size();

            return filterResults;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
           if (results != null && results.count > 0) {
                notifyDataSetChanged();
            } else {
                notifyDataSetInvalidated();
            }
        }
    }

1
这是给我的同伴们的
我在xamarin示例中找到了一个自定义适配器/过滤器,但是其中有一个错误。以下代码是修复后的代码。有问题的代码已经被注释掉。 解释:示例代码在PerformFilter中更新适配器,但是适配器的notifydatachanged在PublishResult中调用。在那段时间内,如果用户设法比您的过滤算法更快地删除文本,则会出现问题。 这与其松散相关: 同时,cheesebaron的示例不起作用,publish result输入值filterresult.values始终为空。这让我浪费了时间。
   class SuggestionsFilter: Filter {

    string[] temp_matchitems_foradapter = new string[1];
    eArrayAdapter customAdapter;
    public SuggestionsFilter(eArrayAdapter adapter): base() {
     customAdapter = adapter;
    }
    protected override Filter.FilterResults PerformFiltering(Java.Lang.ICharSequence constraint) {
     FilterResults results = new FilterResults();
     if (constraint != null) {
      var searchFor = constraint.ToString();

      var matches = customAdapter.AllItems.Where(i =>  i.ToString().IndexOf(searchFor) >= 0 );


      #region  !!!!! FOCUS HEREEEEEEEEEE !!!!!      
      // WRONG----  dont update  adapter here. 
      //adapter.matchitems=  matches.ToArray();
      // RİGHT
      temp_matchitems_foradapter = matches.ToArray();
      #endregion 

      //this doesnt populate filtered view
      results.Values = FromArray(matches.Select(r => r.ToJavaObject()).ToArray());;
      results.Count = matches.Count();
     }
     return results;
    }

    protected override void PublishResults(Java.Lang.ICharSequence constraint, Filter.FilterResults results) 
    {
     //  update customAdapter.matchitems here  and notifychanges
     customAdapter.MatchItems = tempmathitems_foradapter;
     customAdapter.NotifyDataSetChanged();
    }

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