Android AutoCompleteTextView аварийно завершает работу при быстром удалении текста

У меня есть список ключевых слов (около 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;
    }

}


person Thilek    schedule 16.01.2014    source источник
comment
почему вы расширяете ArrayAdapter?   -  person pskink    schedule 16.01.2014
comment
@pskink обычно, когда я делаю адаптер для списка / счетчика, я использовал адаптер массива для управления своим списком ... на основе некоторых примеров, которые я видел ... но есть ли лучший способ сделать это?   -  person Thilek    schedule 16.01.2014
comment
см. мой ответ здесь stackoverflow.com/questions/19858843/   -  person pskink    schedule 16.01.2014
comment
@pskink я заметил, что вы использовали simpleCursorAdapter, но можно ли его использовать со списком объектов? потому что в моем случае, когда пользователь нажимает на ключевое слово, мне нужно, чтобы объект был предоставлен, чтобы я мог получить другие параметры, прикрепленные к ключевому слову в качестве объекта.   -  person Thilek    schedule 16.01.2014
comment
конечно, вы можете добавить все, что хотите, к возвращаемому курсору, или вы можете просто использовать требуемый столбец _id для хранения индекса исходного массива объектов.   -  person pskink    schedule 16.01.2014
comment
@pskink спасибо. попробую..   -  person Thilek    schedule 16.01.2014


Ответы (3)


потому что performFiltering выполняется в рабочем потоке. И вы назначаете свою переменную searchTextAutoSuggestList в этом потоке, но вы должны изменить данные адаптера только в потоке пользовательского интерфейса. Также метод publishResults выполняется в потоке пользовательского интерфейса, поэтому здесь вам не нужны никакие обработчики.

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

}
person Autocrab    schedule 16.01.2014
comment
спасибо за ответ .. это работает .. самое смешное, что моя первая реализация была основана на примере с сайта разработчика Google .. Интересно, какие-либо другие пользователи сталкивались с такой проблемой, как у меня .. developments.google.com/places/training/autocomplete-android" rel="nofollow noreferrer">developers.google.com/places/training/autocomplete-android - person Thilek; 16.01.2014
comment
Привет, Тилек, у меня такая же ошибка. Проблема заключалась в том, что мы меняем ссылку «searchTextAutoSuggestList». Вместо searchTextAutoSuggestList = filterResults.values ​​создайте searchTextAutoSuggestList.addAll((List‹SearchTextAutoSuggest›)filterResults.values) и синхронизируйте экземпляр searchTextAutoSuggestList вместо filterResults. - person Mael; 24.01.2014
comment
@ Маэль, теперь это имеет смысл. Спасибо ;) - person Thilek; 03.02.2014
comment
вместо звонка notifyDataSetInvalidated() я звонил notifyDataSetChanged() в обоих случаях. Вызов предыдущего решил мою проблему. - person Ahmet Gokdayi; 12.03.2020

Я столкнулся с той же проблемой, и после долгих отладок и исследований я решил проблему, переопределив 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();
            }
        }
    }
person Zafar Imam    schedule 01.05.2017

это для моих коллег из xamarin.android

я нашел собственный адаптер/фильтр из примеров xamarin, но в этом примере есть ошибка. приведенный ниже код является фиксированным . проблемный код комментируется

объяснение: код примера обновлял адаптер в PerformFilter . но адаптер notifydatachanged вызывается в PublishResult . в этой задержке, если пользователю удастся удалить текст быстрее, чем ваш алгоритм фильтрации, то БУОООООООООООООООООООООООООООООООООООООООО!!!!

это слабо связано, но: также пример с сырным бароном не работает, публикация входного значения результата 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();
    }
person bh_earth0    schedule 09.07.2018