Android RecyclerView Inconsistency detected崩溃原因及解决方案

1,935 阅读2分钟

崩溃原因:

出现这个问题的主要原因是,对RecyclerView的数据操作混乱导致的。比如notifyDataSetChanged后还没结束又调用 notifyItemInserted、notifyItemMoved等CRUD方法,造成数据的不一致。请仔细检查对数据集操作的事件流是否存在问题。

复现步骤:

  1. A界面recyclerview列表显示一些数据,比如10个数据
  2. B处对A界面recyclerview列表数据进行删减,比如还剩7个数据
  3. 回到A界面还是显示之前的10个数据,但是超过当前实际7个数据

解决方案:

一、捕获异常

自定义RecyclerView LayoutManager的包装类,比如在LinearLayoutManager的onLayoutChildren()方法里try-catch捕获该异常。

import android.content.Context;
import android.util.AttributeSet;

import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

public class WrapContentLinearLayoutManager extends LinearLayoutManager {
    public WrapContentLinearLayoutManager(Context context) {
        super(context);
    }


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


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


    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
    }
}

二、数据集一致

在进行数据移除和数据增加时,需要保证RecyclerView的Adapter中的数据集和移除、添加等操作后的数据集保持一致!这里,前者是在该Adapter内部,假设叫做内部数据集,后者是开发人员传过给Adapter的,假设叫外部数据集。更新RecyclerView数据时,需要保证外部数据集和内部数据集实时保持一致。 外部数据集同步到内部数据集,一般使用下面的方法:

  • notifyItemRangeRemoved();
  • notifyItemRangeInserted();
  • notifyItemRangeChanged();
  • notifyDataSetChanged();

需要注意的是,使用notifyDataSetChange()方法更新内部数据集,没有默认的动画效果,同时更新数据的效率页不如上面的方法,官方不推荐使用这种方式更新数据集。

举例来说明,在RecyclerView的Adapter里,写一段发生异常的错误代码,如下:

public void notifyData(List<PoiItem> poiItemList) {
    if (poiItemList != null ) {
        mPoiItems.clear();
        mPoiItems.addAll(poiItemList);
        notifyItemRangeChanged(0, poiItemList.size());
    }}

错误分析:mPoiItems是外部数据集,对该外部数据集做了两个操作:先移除数据,然后添加数据,之后notify数据集。这里,添加数数据时(Adapter的内部数据集内容还处在外部数据集移除数据之前),造成了内部和外部数据集不一致。 这是一方面,另一方面使用了notifyItemRangeChanged()来更新数据,如果poiItemList传过来的新数据和原来mPoiItems的数据数量不一致,就会出现内部数据集和外部数据在同步后不一致,从而报错。 综上修复后,运行正常的代码如下:

public void notifyData(List<PoiItem> poiItemList) {
    if (poiItemList != null) {
        int previousSize = mPoiItems.size();
        mPoiItems.clear();
        notifyItemRangeRemoved(0, previousSize);
        mPoiItems.addAll(poiItemList);
        notifyItemRangeInserted(0, poiItemList.size());
    }}

参考: