ListView过滤器:IllegalStateException

时间:2013-03-27 10:17:03

标签: java android listview filter illegalstateexception

为了理智,我在这里张贴这个,因为我相信我现在无法自己解决这个问题。我发布了所有要知道的内容,因为我认为为什么我过去没有解决这个问题的原因是因为我没有发布所有内容。到目前为止,我已经剥离了代码,基本上它就像它可以获得的那样轻。但仍然会发生此错误(尽管非常非常罕见):

03-27 10:57:27.783: E/java.lang.IllegalStateException(4929): 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(16908298, class android.widget.ListView) with Adapter(class de.innosoft.android.mobileserviceclient.activities.ListEinsaetze$ArrayAdapterEinsatz)]
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929): 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(16908298, class android.widget.ListView) with Adapter(class de.innosoft.android.mobileserviceclient.activities.ListEinsaetze$ArrayAdapterEinsatz)]
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.ListView.layoutChildren(ListView.java:1544)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.AbsListView.onLayout(AbsListView.java:1994)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.AdapterView.updateEmptyStatus(AdapterView.java:747)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.AdapterView.checkFocus(AdapterView.java:720)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:812)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:5958)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at de.innosoft.android.mobileserviceclient.ui.DynamicArrayAdapter.notifyDataSetChanged(DynamicArrayAdapter.java:291)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at de.innosoft.android.mobileserviceclient.ui.DynamicArrayAdapter.notifyDataSetChanged(DynamicArrayAdapter.java:268)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at de.innosoft.android.mobileserviceclient.ui.DynamicArrayAdapter$DynamicAdapterFilter.publishResults(DynamicArrayAdapter.java:119)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.widget.Filter$ResultsHandler.handleMessage(Filter.java:282)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.os.Handler.dispatchMessage(Handler.java:99)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.os.Looper.loop(Looper.java:137)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at android.app.ActivityThread.main(ActivityThread.java:5039)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at java.lang.reflect.Method.invokeNative(Native Method)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at java.lang.reflect.Method.invoke(Method.java:511)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
03-27 10:57:27.783: E/java.lang.IllegalStateException(4929):    at dalvik.system.NativeStart.main(Native Method)

在这篇文章的最底部,我解释了这个错误发生的时间。但首先是代码。我有这个TextWatcher:

edSearch.addTextChangedListener(new TextWatcher() {
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                }

                String text = null;

                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                    text = s.toString();
                }

                @Override
                public void afterTextChanged(final Editable s) {
                    if (text != null && !text.equals(s.toString())) {
                        adapter.getFilter().filter(s.toString());
                    }
                    text = null;
                }
            });

此线程在边线上运行,每200毫秒触发一次textwatcher。我会疯狂地手动测试:

new Thread(new Runnable() {
            public void run() {
                Runnable r = new Runnable() {
                    public void run() {
                         dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
                         KeyEvent.KEYCODE_S));
                         dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
                         KeyEvent.KEYCODE_S));
                    }
                };

                Runnable r2 = new Runnable() {
                    public void run() {
                         dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN,
                         KeyEvent.KEYCODE_DEL));
                         dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
                         KeyEvent.KEYCODE_DEL));
                    }
                };
                while (true) {

                    runOnUiThread(r);
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        ExceptionHandler.handle(e);
                    }
                    runOnUiThread(r2);
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        ExceptionHandler.handle(e);
                    }
                }
            }
        }).start();
    }

这是我的适配器:

private class ArrayAdapterEinsatz extends DynamicArrayAdapter<Einsatz> {

        public ArrayAdapterEinsatz(int textViewResourceId) {
            super(ListEinsaetze.this, textViewResourceId, new ArrayList<Einsatz>());
        }

        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {

            Einsatz einsatz = getFilteredItems().get(position);

            View rowView = convertView;
            if (rowView == null) {
                LayoutInflater inflater = ListEinsaetze.this.getLayoutInflater();
                rowView = inflater.inflate(R.layout.eintrag_einsatz, null);
                ViewHolder viewHolder = new ViewHolder();

                viewHolder.tvId = (TextView) rowView.findViewById(R.id.eeinsatzid);
                viewHolder.tvDatumVonbis = (TextView) rowView.findViewById(R.id.eeinsatzvonbis);
                viewHolder.tvKundenName = (TextView) rowView.findViewById(R.id.eeinsatzkundenname);
                viewHolder.tvKundenOrt = (TextView) rowView.findViewById(R.id.eeinsatzort);
                viewHolder.tvEntfernung = (TextView) rowView.findViewById(R.id.eeinsatzentfernung);
                viewHolder.iveRotate = (ImageViewEffects) rowView.findViewById(R.id.eeinsatzrotate);
                viewHolder.iveRotate.setBitmap(Utils.drawableToBitmap(R.drawable.ic_refresh_1, 22), ImageViewEffects.ROTATE);
                viewHolder.ivAnsprechpartnerVorhanden = (ImageView) rowView.findViewById(R.id.eeinsatzimageansprechvorhanden);
                viewHolder.ivBelegeVorhanden = (ImageView) rowView.findViewById(R.id.eeinsatzimagebelegevorhanden);
                viewHolder.ivTaetigkeitVorhanden = (ImageView) rowView.findViewById(R.id.eeinsatzimagetaetigkeitvorhanden);

                rowView.setTag(viewHolder);
            }

            final ViewHolder holder = (ViewHolder) rowView.getTag();

            return rowView;
        }

    }

最后这是基础适配器类。注意最底部的实际过滤器函数,它根据新的Random()过滤.sextBoolean()不能更简单。

public class DynamicArrayAdapter<T> extends BaseAdapter implements Filterable {

  public class DynamicAdapterFilter extends Filter {

    private CharSequence m_Filter = null;
    private List<T> m_FilteredItems = null;

    public DynamicAdapterFilter() {
      m_FilteredItems = new ArrayList<T>();
    }

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {


      FilterResults r = new FilterResults();
      List<T> items = null;
      m_Filter = constraint;

      if (constraint == null) {
        items = m_AllItems;
      } else {
        items = m_FilteredItems;
        items.clear();

        synchronized (SyncLock) {
          List<T> l = new ArrayList<T>(m_AllItems);
          for (T item : l) {
            if (DynamicArrayAdapter.this.filter(item, constraint)) {
              items.add(item);
            }
          }
        }
      }

      r.values = items;
      r.count = items.size();

      return r;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {

      m_Items = (List<T>) results.values;

      notifyDataSetChanged();

    }

    public void refresh() {
      if (m_Filter != null) {

        filter(m_Filter);
      }
    }

    public void refresh(FilterListener listener) {
      if (m_Filter != null ) {

        filter(m_Filter, listener);
      }
    }
  }


  private final Object SyncLock = new Object();
  private Context m_Context = null;
  private LayoutInflater m_Inflater = null;
  private int m_DelegateResourceId;
  private boolean m_ChangeNotificationsEnabled = true;
  private List<T> m_AllItems = null;
  private List<T> m_Items = null;
  private DynamicAdapterFilter m_Filter = null;


  public DynamicArrayAdapter(Context context, int delegateResourceId) {
    this(context, delegateResourceId, new ArrayList<T>());
  }

  public DynamicArrayAdapter(Context context, int delegateResourceId, List<T> items) {
    m_Context = context;
    m_DelegateResourceId = delegateResourceId;
    initItems(items);

    m_Inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

  }

  public void initItems(List<T> items) {
    m_AllItems = m_Items = items;
  }

  /**
   * returns the filtered item count (or all item count if no filter exists)
   */
  @Override
  public int getCount() {
        return m_Items != null ? m_Items.size() : 0;
  }

  /**
   * returns the item at the position in the filtered list (or all item count if
   * no filter exists)
   */
  @Override
  public T getItem(int position) {
    return m_Items.get(position);
  }

  /**
   * holt position von item, exakte gleiche implementation wie ArrayAdapter<T>
   * 
   * @param item
   * @return
   */
  public int getPosition(T item) {
    return m_Items.indexOf(item);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public long getItemId(int position) {

    return position;
  }

  /**
   * returns the list view item delegate view. this view is inflated if it isn't
   * already inflated. Override this method to perform custom view operations
   * for the list view item.
   */
  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
      convertView = m_Inflater.inflate(m_DelegateResourceId, parent, false);
    }

    return convertView;
  }

  /**
   * returns the filter. this filter is created if it hasn't yet been created.
   */
  @Override
  public DynamicAdapterFilter getFilter() {
    if (m_Filter == null) {
      m_Filter = new DynamicAdapterFilter();
    }

    return m_Filter;
  }

  /**
   * If notifications are enabled, this notifies the base adapter that the
   * backing data has changed
   */
  @Override
  public void notifyDataSetChanged() {
    notifyDataSetChanged(false);
  }

  /**
   * If forced or notifications are enabled, this notifies the base adapter that
   * the backing data has changed
   * 
   * @param force
   *          force notify even if notifications are disabled
   */
  public void notifyDataSetChanged(boolean force) {
    if (force || m_ChangeNotificationsEnabled) {

      super.notifyDataSetChanged();
    }
  }

  /**
   * @return UI context
   */
  public Context getContext() {
    return m_Context;
  }

  /**
   * @return UI inflater
   */
  public LayoutInflater getLayoutInflater() {
    return m_Inflater;
  }

  /**
   * @return Layout ID of the list view item delegate
   */
  public int getDelegateResourceId() {
    return m_DelegateResourceId;
  }

  /**
   * @return true if notifications are enabled, false otherwise
   */
  public boolean getChangeNotificationsEnabled() {
    return m_ChangeNotificationsEnabled;
  }

  /**
   * Set whether notifications are enabled or not
   */
  public void setChangeNotificationsEnabled(boolean value) {
    m_ChangeNotificationsEnabled = value;
  }

  // internal mechanism to handle automagical updates of the list items
  private void onDataChange() {


    if (m_Filter == null) {
      notifyDataSetChanged();
    } else {
      m_Filter.refresh();
    }
  }

  /**
   * @return All (unfiltered) items
   */
  public List<T> getAllItems() {
    return m_AllItems;
  }

  /**
   * Set the backing list
   */
  public void setAllItems(List<T> value) {
    m_AllItems = value;

    onDataChange();
  }

  /**
   * @return Current filtered items, or all items if there is no filter
   */
  public List<T> getFilteredItems() {
    return m_Items;
  }

  public void add(T item) {
    synchronized (SyncLock) {
      m_AllItems.add(item);
    }

    onDataChange();
  }

  public void insert(T item, int index) {
    synchronized (SyncLock) {
      m_AllItems.add(index, item);
    }

    onDataChange();
  }

  public void remove(T item) {
    synchronized (SyncLock) {
      m_AllItems.remove(item);
    }

    onDataChange();
  }

  public void removeAt(int index) {
    synchronized (SyncLock) {
      m_AllItems.remove(index);
    }

    onDataChange();
  }

  public void clear() {
    synchronized (SyncLock) {
      m_AllItems.clear();
    }

    onDataChange();
  }

  public void sort(Comparator<? super T> comparator) {
    synchronized (SyncLock) {
      Collections.sort(m_Items, comparator);
    }

    onDataChange();
  }

  protected boolean filter(T item, CharSequence constraint) { // AR
    return new Random().nextBoolean();

  }
}

所以基本上程序只是每200毫秒触发一次textwatcher。因此,textwatcher一次又一次地过滤列表。没有其他任何东西在计划中幸福。

如果我没有设置断点,程序运行正常,没有错误。

当我断开线

时发生错误
adapter.getFilter().filter(s.toString());

在TextWatcher中按住F8(继续)按下(这似乎会给程序流程带来不规则性,我只能想象?)。没有断点就不会发生错误。

如果我放

那么顺便说一句
adapter.notifyDatasetChanged()

adapter.getFilter().filter(s.toString());

100%的时间会发生同样的错误(我知道在publishResults中调用了notifyDatasetChanged,所以我不知道 需要它,但它是否仍然是正确的行为?)。

TL; DR:

我的代码(特别是基本适配器代码)是否安全? (好吧不能不会发生错误)

修改

如果没有设置断点,也会出现

错误我只有一个......

2 个答案:

答案 0 :(得分:0)

尝试移动

 adapter.getFilter().filter(s.toString());

在runnable中使用view.post()连接到ui线程。

它“可以”解决你的问题。

答案 1 :(得分:0)

不要添加textwatcher,只需将适配器设置为编辑文本,如下所示。

edSearch.setAdapter(适配器);

并覆盖公共过滤器getFilter()和 protected void publishResults(CharSequence contraint,FilterResults results)
方法

这会解决你的问题。遇到同样的问题并以这种方式解决了它:)