【问题标题】:Exception java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder异常 java.lang.IndexOutOfBoundsException:检测到不一致。无效的视图支架适配器 positionViewHolder
【发布时间】:2017-03-28 08:34:44
【问题描述】:

我正在开发Android应用程序并在Play商店发布后,我多次遇到这个问题。

Exception java.lang.IndexOutOfBoundsException: Inconsistency detected.Invalid view holder adapter positionViewHolder{2f9d83c position=11 id=-1, oldPos=-1, pLpos:-1 no parent}
android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition (RecyclerView.java:5297)
android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline (RecyclerView.java:5479)
android.support.v7.widget.GapWorker.prefetchPositionWithDeadline (GapWorker.java:282)
android.support.v7.widget.GapWorker.flushTaskWithDeadline (GapWorker.java:336)
android.support.v7.widget.GapWorker.flushTasksWithDeadline (GapWorker.java:349)
android.support.v7.widget.GapWorker.prefetch (GapWorker.java:356)
android.support.v7.widget.GapWorker.run (GapWorker.java:387)
android.os.Handler.handleCallback (Handler.java:815)
android.os.Handler.dispatchMessage (Handler.java:104)
android.os.Looper.loop (Looper.java:207)
android.app.ActivityThread.main (ActivityThread.java:5737)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:789)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:679)

我搜索了很长时间关于这个问题并应用了许多解决方案,但仍然引发了异常

我应用的解决方案之一是制作自定义 LinearLayoutManager:

public class WrapContentLinearLayoutManager  extends LinearLayoutManager {

    public WrapContentLinearLayoutManager(Context context) {
        super(context);
    }

    //... constructor
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
            setAutoMeasureEnabled(false);
        } catch (IndexOutOfBoundsException e) {
            Log.e("Error", "IndexOutOfBoundsException in RecyclerView happens");
        }
    }

    public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
        setAutoMeasureEnabled(false);
    }

    public WrapContentLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setAutoMeasureEnabled(false);
    }

    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }
}

在我的应用程序中依赖于具有 Load moreSwipe to Refresh 功能的 RecyclerView

我使用 Volley 从 Web 服务获取数据以进行 Load moreSwipe to Refresh

要在 RecyclerView 中加载更多项目,在 Volley Response 的回调中,我将新项目附加到 ArrayList 并通知适配器,如下所示:

    final int positionStart = list.size() - 1;
    final int itemCount = response.length();
    for (int i=0;i<response.length();i++)
    {
        try 
        {
            obj = response.getJSONObject(i);
            list.add(new ItemEntry(obj.getInt("Id"),obj.getString("Title").trim()obj.getBoolean("isFavorite"));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if(index == 0)
        { 
            // for first Initialize for adapter 
            adapter = new MyAdapter(latestEntryList,getActivity());
            adapter.setOnItemClickCallBack(mainFragment);
            recyclerView.setAdapter(adapter);
        }
        else
        {
            // for load more items
            recyclerView.post(new Runnable() 
            {
                @Override
                public void run() 
                {
                   adapter.updateItems(positionStart,itemCount);
                }
            });
        }
    }

Adapter.UpdateItems 的代码

  public void updateItems(int positionStart,int itemCount) {
        itemsCount = getItemCount();
        this.notifyItemRangeInserted(positionStart,itemCount);
    }

如果加载更多,我会执行以下操作;在 OnRefresh 中

   public void onRefresh() {
        listFragment.LatestEntry(0,false);
// 0 the index and False isAppend to list of not
    } 

LatestEntry 的代码是:

   private void LatestEntry(final int index, final boolean isAppend)
        {
        String customerId = "123";
        String uri = String.format(BASE_URL+"/api/Entry/LatestEntry?customerId=%s&index=%s",customerId,index);

            JsonArrayRequest LatestEntryJsonArrayRequest = new JsonArrayRequest(Request.Method.GET, uri,
                null, new Response.Listener<JSONArray>() {
                @Override
                public void onResponse(JSONArray response) 
                   {
                        JSONObject obj = new JSONObject();
                        if(response.length() > 0 && !isAppend) 
                        {
                            int startPosition = 0;
                            int preSize;

                            if(adapter != null)
                                preSize = adapter.entries.size();
                            else
                                preSize = 0;
                            if(preSize > 0 && EntryList.size() > 0) {
                                EntryList.clear();
                                adapter.entries.clear();
                                adapter.notifyItemRangeRemoved(startPosition, preSize);
                            }
                        }

                        final int positionStart = EntryList.size() - 1;
                        final int itemCount = response.length();
                        for (int i=0;i<response.length();i++)
                        {
                            try {
                                obj = response.getJSONObject(i);
                                list.add(new ItemEntry
                               (obj.getInt("Id"),obj.getString("Title").trim()
                                  obj.getBoolean("isFavorite"));

                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                        }

                        if(index == 0) 
                        { // for first Initialize for adapter 
                            adapter = new MyAdapter(latestEntryList,getActivity());
                            adapter.setOnItemClickCallBack(mainFragment);
                            recyclerView.setAdapter(adapter);
                        }
                        else
                        {
                            // for load more items
                            recyclerView.post(new Runnable() 
                            {
                                @Override
                                public void run() {
                                   adapter.updateItems(positionStart,itemCount);
                                }
                            });
                        }
                    }
                    setListShownNoAnimation(true);
                    if(mainFragment.swipeRefreshLayout.isRefreshing())
                      mainFragment.swipeRefreshLayout.setRefreshing(false);
                }
            }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Toast.makeText(MyApplication.getAppContext(),error.getMessage(),Toast.LENGTH_LONG);
                }
            }
            );

            LatestEntryJsonArrayRequest.setTag("LatestEntry");
            KidsSingleton.getInstance().addToRequestQueue(LatestEntryJsonArrayRequest);
        }

请注意以下几点:

  1. Recyclerview 版本为:recyclerview-v7:25.3.0
  2. 我在 ViewPager 中有三个 RecylerView
  3. 每个 ViewPager 都包含 Fragment,而 Fragment 包含带有 RecylerView 的 ListFragment。
  4. 所有 RecylcrViews 都有一个适配器。

谁能帮助解决这个问题?

【问题讨论】:

  • 你的 reccyleview 支持库的版本是多少?
  • 我用这个版本recyclerview-v7:25.3.0
  • 仅供参考:onLayoutChildren() 在这里不是问题 - 您的堆栈跟踪不会在列表中显示它。
  • 这里有完全相同的问题。看起来像 prefetch 功能错误。这些错误仅在 Google 在 25.1.0 添加此功能后出现。

标签: android


【解决方案1】:

我有类似的情况 - viewpagers 作为回收者视图的项目。在添加\删除项目时,这些“等待被删除”项目有一些动画。因此,当我调用 remove item 时,动画仍在播放时,我得到了:

IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling

为避免此异常,我创建了一个处理程序来延迟删除项目。但!这是错误的。如果您更改了适配器 RV 的数据,则应立即在没有任何处理程序或多线程通知这些更改的情况下。 因此,请考虑将这种方式更改为不需要单独线程的更新方式。

【讨论】:

    【解决方案2】:

    RecyclerView$Recycler.validateViewHolderForOffsetPosition 抱怨没有收到有效的ViewHolder 项目,而它似乎在第 11 个项目的偏移位置。

    假设您在向适配器添加更多项目时可能忘记为ViewHolder 充气......处理方式似乎是错误的;虽然普通,只需要更新数据数组,然后用例如通知适配器。 notifyDataChanged();想想当直接向适配器添加项目时,这些通知是在内部触发的。

    swipeRefreshLayout.setRefreshing(false) 可能应该发生在回调方法中,以便加载完成后滚轮动画消失。

    也许检查此example 以了解框架触发事件的顺序(因此通常是问题所在,即创建功能,框架已经带来了)...步入可能还提供一个线索,为什么没有有效的ViewHolder 被夸大该项目。

    【讨论】:

    • 我尝试了你的建议,现在在 Loading more 我将数据添加到临时列表,然后更新适配器的列表一次,然后调用 notifyItemRangeInserted,但在我发布应用程序之后Google Play 再次出现异常。
    • @MohammedK.Abuhalib 只能猜测,而没有看到崩溃报告; ProGuard 混淆的可能性很大(甚至可能对该类有-dontnote-dontwarn 标志,因此不会抱怨未知类)...删除(或注释掉)这些配置规则将是第一个我会采取的步骤。上传mapping.txt 也很有用。
    • 我不使用 ProGuard 混淆,我不使用 -dontnote-dontwarn 标志,而且 proguard-rules.pro 文件是空的。
    【解决方案3】:

    可能是您设置到适配器的列表已更改,但尚未调用 notifyDataSetChanged。 就我而言,我延迟了对 notifyDataSetChanged 的​​调用。但是适配器的 getItemCount() 已更改为另一个值,因此它崩溃了。

    【讨论】:

      【解决方案4】:

      致电notifyItemRangeChanged(positionStart,itemCount)

      致电notifyItemRangeInserted(positionStart,itemCount);之前

      为我工作。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-10-23
        • 2016-06-09
        • 1970-01-01
        • 1970-01-01
        • 2017-11-02
        • 2017-10-04
        • 2021-11-29
        相关资源
        最近更新 更多