【问题标题】:IndexOutofBounexception in search adapter onbind method?搜索适配器onbind方法中的IndexOutofBounexception?
【发布时间】:2020-02-22 11:44:27
【问题描述】:

只要我开始搜索/过滤我的 recyclerview 列表结果,我就会不断从搜索适配器中的 onBind 方法中获取 IndexOutofBoundsException。 这是一个例外:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 0(offset:0).state:25 android.support.v7.widget.RecyclerView{3247b3d VFED..... ......ID 0,0-1440,5550 #7f090216 app:id/search_guests_recycler_view}, adapter:com.myapp.ui.registervisitor.searchGuests.SearchGuestListAdapter@2433f32, layout:android.support.v7.widget.LinearLayoutManager@68f5483, context:com.myapp.ui.registervisitor.searchGuests.SearchGuestActivity@843713
        at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5923)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
        at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
        at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1557)
        at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
        at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
        at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3924)
        at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:3336)
        at android.view.View.measure(View.java:24545)
        at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:735)
        at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:481)
        at android.view.View.measure(View.java:24545)
        at android.widget.ScrollView.measureChildWithMargins(ScrollView.java:1414)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.widget.ScrollView.onMeasure(ScrollView.java:452)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.support.design.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:733)
        at android.support.design.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:805)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.support.design.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:733)
        at android.support.design.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:805)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:24545)
        at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:735)
        at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:481)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
        at android.view.View.measure(View.java:24545)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
        at android.view.View.measure(View.java:24545)

我在网上尝试了几篇文章,但没有运气。我尝试过的一些文章是:RecyclerView Adapter onBind methodRecyclerView: Inconsistency detected. Invalid item position

我针对此问题的适配器代码位于: https://pastebin.com/VxsWWMiS

以及相应的过滤活动代码:

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                mSearchGuestListAdapter.getFilter().filter(query);

                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                mSearchGuestListAdapter.getFilter().filter(newText);
                mSearchGuestListAdapter.notifyDataSetChanged();
                mSearchGuestListAdapter.setFilter(newText);

                if(mSearchGuestListAdapter.getItemCount() == 0){


                    String sourceString = "No match found for <b>" + newText + "</b> ";
                    mNoMatchTextView.setText(Html.fromHtml(sourceString));
                } else {
                    mEmptyRelativeLayout.setVisibility(View.GONE);
                    mRecyclerView.setVisibility(View.VISIBLE);
                }
                return false;
            }
        });

如果需要,很乐意分享任何其他详细信息。任何想法如何解决这个问题?

这是我的完整活动代码:https://pastebin.com/5qDN4yh9

这是调用 API 的演示者类:https://pastebin.com/YGiPGn8Z

这是模型类:https://pastebin.com/WCkPFnvU

这是一个 json 样本:https://pastebin.com/82R1zBHP

这是调用 recyclerview 的 xml 代码:https://pastebin.com/Z3QxPkSL "search_guests_recycler_view"

【问题讨论】:

  • 使用已经实现Filterable的适配器,为什么要重新发明轮子?例如,这是完整的、工作的 Filterable 适配器:pastebin.com/raw/LEena3pM
  • 我已经在我的适配器中使用了可过滤的工具
  • 这就是为什么我说:“为什么要重新发明轮子?”是否要在每个要过滤的适配器中实现相同的代码?
  • 我没有任何其他需要过滤的代码,但由于这似乎可以满足我的过滤器需求,您能否帮助找出可能导致崩溃的我可能缺少的内容?我想知道它是否像添加 notifydatasetchanged 或更新项目的位置一样简单?尝试了几件事,但没有运气
  • 你没有设置 filterResults.count - 但我不能 100% 确定这是否是你自定义 Filter 中的唯一错误

标签: java android android-layout android-recyclerview android-adapter


【解决方案1】:

您的代码中有多种考虑因素可能会导致适配器出现这种不一致。

要了解 Filter 类的工作原理,您应该注意,根据official documentation

过滤操作通过调用 filter(java.lang.CharSequence) 或 filter(java.lang.CharSequence, android.widget.Filter.FilterListener) 异步执行 .当这些方法被调用时,过滤请求会被发布到请求队列中并稍后处理。对这些方法之一的任何调用都将取消任何先前未执行的过滤请求。


1。使用单个过滤器实例

Filter 类执行调用performFiltering() 方法的后台任务。为避免在键入时重叠过滤器,您应该避免使用多个过滤器实例。在您的 SearchGuestListAdapter 类中:

不正确:

@Override
public Filter getFilter() {
    // WRONG: Return new instance of Filter every time getFilter() is called
    return new Filter() {
        @Override
        protected FilterResults performFiltering(CharSequence charSequence) {
            // Perform filtering...
            return anything;
        }

        @Override
        protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
            // Update and Notify adapter...
        }
    };
}

改为:

private final Filter filter = new Filter() {
    @Override
    protected FilterResults performFiltering(CharSequence charSequence) {
        // Perform filtering...
        return anything;
    }

    @Override
    protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
        // Update and Notify adapter...
    }
};

@Override
public Filter getFilter() {
    // CORRECT: Always use the same Filter instance.
    return this.filter;
}

2。您不应该在 performFiltering() 中修改适配器变量

performFiltering() 方法在后台线程中执行。在这里你可以做一个很长的工作,从数据库中读取数据,如果你需要的话,甚至可以发出一个 webservice 同步请求。

但是,如果您在此处更改适配器列表,则会导致屏幕上显示的 RecyclerView 与适配器列表的内容不一致。相反,您必须构建一个将使用 FilterResults 返回的临时列表。

使用以下结构修复 performFiltering():

@Override
protected FilterResults performFiltering(CharSequence charSequence) {
    // Here is Background Thread, never alter adapter list in this method
    String charString = charSequence.toString();
    List<RegisterGuestList.Guest> filteredList = new ArrayList<>();

    if (charString.isEmpty()) {
        filteredList.addAll(mSearchGuestListResponseList);
    } else {
        ... // fill filteredList as you did previously
    }

    FilterResults filterResults = new FilterResults();
    filterResults.values = filteredList;
    filterResults.count = filteredList.size(); // count is optional
    return filterResults;
}

@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
    // Here is Main Thread, safe to update list and notify adapter
    mSearchGuestListResponseListFiltered = (ArrayList<RegisterGuestList.Guest>) filterResults.values;
    searchText = charSequence.toString();
    notifyDataSetChanged();
}

3。过滤完成时使用 FilterListener 获得通知。

SearchGuestActivityonQueryTextChange() 中,您希望检查适配器列表大小以显示或隐藏空视图。由于过滤器在后台线程中工作,您必须使用FilterListener 执行过滤器调用以检查空列表。

删除 SearchGuestListAdapter 的 setFilter() 方法并使用以下结构修复 SearchGuestActivity 的 onQueryTextChange()

@Override
public boolean onQueryTextChange(String newText) {
    mSearchGuestListAdapter.getFilter().filter(newText, new Filter.FilterListener() {
        @Override
        public void onFilterComplete(int count) {
            if (count == 0) {
                // Show empty view...
            } else {
                // Show recyclerView...
            }
        }
    });

    // Don't call notifyDataSetChanged() or setFilter() here!
    // Adaper will notified by publishResults() method
    // mSearchGuestListAdapter.setFilter(newText);
    // mSearchGuestListAdapter.notifyDataSetChanged();

    return false;
}

【讨论】:

    【解决方案2】:

    上面的堆栈跟踪似乎与您的代码没有隐式相关。以及由动画的内部 RecyclerView 实现引起的问题。还有described here

    1) 可能的解决方法,只是清除活动的ViewHolders 池,这可能会等待动画。并且可以抛出IndexOfBoundException。在任何notifyDataSetChanged 之前调用以下方法。

    mRecyclerView.getRecycledViewPool().clear();
    mAdapter.notifyDataSetChanged();
    

    2) 确保您的适配器中没有 notifyItemRangeChangednotifyItemAdded(和类似的)方法。使用notifyDataSetChanged。当然你可以避免这种情况,但是你需要使用单个集合并且每次都调试正确的位置才能使用这个特性。

    3) 最后如您所见,这完全是关于您的项目的动画和RecyclerView 的内部行为。要从另一端解决它,您可以覆盖 LayoutManager 并删除动画。这将确保您的实施中的职位没有问题。

    /**
     * Could be the same for Grid LayoutManager or Linear
     **/
    private static class NpaLinearLayoutManager extends LinearLayoutManager {
        /**
         * Disable predictive animations. There is a bug in RecyclerView which causes views that
         * are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
         * adapter size has decreased since the ViewHolder was recycled.
         */
        @Override
        public boolean supportsPredictiveItemAnimations() {
            return false;
        }
    // .....
    }
    

    【讨论】:

    • 线性布局管理器有什么用吗?如何将其注入我的代码中?
    • 另外,在我的活动中,我确实调用了:Objects.requireNonNull(mRecyclerView).getRecycledViewPool().clear();每次我调用 notifydatasetchanged。我不使用 notifyItemRangeChanged 或 item added。在活动中设置适配器时,我将 setitemanimator 设置为 null。尝试了包括该链接在内的所有解决方案,到目前为止没有运气:(
    • @AngelaHeely 检查答案。正如我提到的,它可以用于任何LayoutManager
    • @AngelaHeely 你有机会尝试吗?
    • 我做到了,但我仍然崩溃。不知道缺少什么:(
    【解决方案3】:

    替换所有出现的mSearchGuestListResponseList
    SearchGuestListAdapter#getItemCount(),
    SearchGuestListAdapter#getItemViewType()
    mSearchGuestListResponseListFiltered;

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-19
    • 1970-01-01
    相关资源
    最近更新 更多