Android SearchView:可筛选的Listview运行时错误

Android SearchView:可筛选的Listview运行时错误,android,listview,android-listview,searchview,android-filter,Android,Listview,Android Listview,Searchview,Android Filter,使用可过滤列表视图时,我偶尔会遇到问题。在过滤配方列表视图时,从searchview中删除文本时,我很少会遇到运行时错误。当使用软键盘快速删除文本时,往往会发生这种情况 90%的情况下,此实现完全按照我的预期工作,但是当快速删除字符时,我会出现以下错误 04-07 11:38:55.221 9591-9591/brd.cms.sup E/dalvikvm﹕ >>>>> Normal User 04-07 11:38:55.221 9591-9591/br

使用可过滤列表视图时,我偶尔会遇到问题。在过滤配方列表视图时,从searchview中删除文本时,我很少会遇到运行时错误。当使用软键盘快速删除文本时,往往会发生这种情况

90%的情况下,此实现完全按照我的预期工作,但是当快速删除字符时,我会出现以下错误

04-07 11:38:55.221    9591-9591/brd.cms.sup E/dalvikvm﹕ >>>>> Normal User
04-07 11:38:55.221    9591-9591/brd.cms.sup E/dalvikvm﹕ >>>>> brd.cms.sup [ userId:0 | appId:10359 ]
04-07 11:38:59.295    9591-9591/brd.cms.sup E/OpenGLRenderer﹕ GL_INVALID_OPERATION
04-07 11:40:58.018    9591-9591/brd.cms.sup E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: brd.cms.sup , PID: 9591
    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. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(2131493035, class android.widget.ListView) with Adapter(class brd.cms.sup.RecipeAdapter)]
            at android.widget.ListView.layoutChildren(ListView.java:1566)
            at android.widget.AbsListView.onLayout(AbsListView.java:2598)
            at android.view.View.layout(View.java:15860)
            at android.view.ViewGroup.layout(ViewGroup.java:4902)
            at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
            at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
            at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
            at android.view.View.layout(View.java:15860)
            at android.view.ViewGroup.layout(ViewGroup.java:4902)
            at android.support.v4.widget.DrawerLayout.onLayout(DrawerLayout.java:890)
            at android.view.View.layout(View.java:15860)
            at android.view.ViewGroup.layout(ViewGroup.java:4902)
            at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
            at android.view.View.layout(View.java:15860)
            at android.view.ViewGroup.layout(ViewGroup.java:4902)
            at android.support.v7.internal.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:502)
            at android.view.View.layout(View.java:15860)
            at android.view.ViewGroup.layout(ViewGroup.java:4902)
            at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
            at android.view.View.layout(View.java:15860)
            at android.view.ViewGroup.layout(ViewGroup.java:4902)
            at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
            at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
            at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
            at android.view.View.layout(View.java:15860)
            at android.view.ViewGroup.layout(ViewGroup.java:4902)
            at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
            at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
            at android.view.View.layout(View.java:15860)
            at android.view.ViewGroup.layout(ViewGroup.java:4902)
            at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2379)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2092)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1267)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6640)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:813)
            at android.view.Choreographer.doCallbacks(Choreographer.java:613)
            at android.view.Choreographer.doFrame(Choreographer.java:583)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:799)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:146)
            at android.app.ActivityThread.main(ActivityThread.java:5635)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
            at dalvik.system.NativeStart.main(Native Method)
下面是我的SearchView.OnQueryTextListener实现

@Override
public boolean onQueryTextChange(String newText) {
    if (recipeAdapter != null) {
        try {
            recipeAdapter.getFilter().filter(newText);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    return false;
}
这是我在RecipeDapter中的私人RecipeFilter

private class RecipeFilter extends Filter{
    List<Recipe> filteredList = new ArrayList<>();


    @Override
    protected FilterResults performFiltering(CharSequence constraint){
            constraint = constraint.toString().toLowerCase();
            FilterResults result = new FilterResults();
            filteredList.clear();
            if (constraint != null && constraint.toString().length() > 0) {
                for (Recipe r : backupList) {
                    if (r.contains(constraint)) {
                        filteredList.add(r);
                    }
                }
            } else {
                filteredList.addAll(backupList);
            }
            result.count = filteredList.size();
            result.values = filteredList;
            return result;
        }


    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        recipeList = (List) results.values;
        if (results != null) {
            notifyDataSetChanged();
        }
        else{
            notifyDataSetInvalidated();
        }
    }
}
私有类RecipeFilter扩展筛选器{
List filteredList=新建ArrayList();
@凌驾
受保护的筛选器结果性能筛选(CharSequence约束){
constraint=constraint.toString().toLowerCase();
FilterResults结果=新的FilterResults();
filteredList.clear();
if(constraint!=null&&constraint.toString().length()>0){
用于(配方r:反向列表){
if(r.contains(constraint)){
filteredList.add(r);
}
}
}否则{
filteredList.addAll(backupList);
}
result.count=filteredList.size();
result.values=filteredList;
返回结果;
}
@凌驾
受保护的void publishResults(CharSequence约束、FilterResults结果){
recipeList=(列表)results.values;
如果(结果!=null){
notifyDataSetChanged();
}
否则{
notifyDataSetionValidated();
}
}
}

任何想法都将不胜感激

了解如何使用
filteredList
recipeList
会有所帮助。但是,您的
ListView
不同步可能是由于缺少同步造成的。
performFiltering()
方法在后台线程上执行。您必须同步那些
列表上的任何突变
…并且不仅在
performFiltering()
方法中,而且在整个适配器中。顺便说一句,我希望您的自定义适配器不是
ArrayAdapter
的扩展。这本身就是另一个问题

下面是一个关于Android的
ArrayAdapter
过滤问题的讨论。最后,您会发现示例代码展示了为适配器编写
过滤器
类的好方法。基本上就是你在这里发布的代码

管理列表

总而言之,您有一个
recipeList
,它保存适配器中的所有项,或者只保存筛选的项。您还有一个存储所有项目的
backupList
。然后在
Filter
类中,您有一个
filterList
,用于将过滤后的数据放入
recipeList

这种方法相当标准……使用两个主要列表来跟踪过滤后的数据和原始数据。使用这些列表的关键是同步和知道使用哪一个

对于任何getter,适配器都应该始终使用来自
recipeList
的数据。例如,
getItem()
getItemId()
,等等。访问此数据不需要同步

对于任何setter,您将执行以下操作

  • 同步化
  • 确定要更新的列表
  • 调用
    notifyDataSetChanged
  • 关于#2,根据您的设置,无论您的
    备份列表是什么,都应该更新。您的
    recipeList
    是否也会得到更新取决于您是否希望在筛选过程中应用突变

    最后,过滤器类需要添加同步。特别是当您将数据从
    备份列表复制到
    过滤器列表时。此外,
    filteredList
    需要是方法
    performFiltering()
    的本地列表,而不是类的全局列表。或者同步整个方法。由于该方法发生在后台线程上,因此多个线程很可能串联执行该方法。这意味着他们都在更改相同的
    filteredList
    …这不好


    另外,在
    publishResults
    中,您需要同步更新
    recipeList
    ,并且当结果为null或空时,您需要调用
    notifyDataSetInvalidated()

    我正在为此实现扩展BaseAdapter。适配器开始使用recipeList,然后在过滤后,使用filteredList收缩结果集,并在recipeList上复制filteredList并通知数据集已更改。当搜索约束为空时,我将原始列表(保存为构造函数中的backupList)复制回配方列表,并再次通知更改的集合。如果这太离谱了,请告诉我,这对我来说都是比较新的非常感谢你!我听从了你的建议,同步这些方法似乎是最重要的,但现在运行得很好。再次感谢!