我们的QA检测到一个错误:当旋转Android设备(Droid Turbo)时,发生了以下recyclerview相关的崩溃:
java.lang.IndexOutOfBoundsException:
不一致。无效项位置2(偏移量:2).state:3
对我来说,它看起来像一个内部错误在RecyclerView,因为我不能想到这是由我们的代码直接引起的任何方式…
有人遇到过这个问题吗?
解决方案是什么?
一个残酷的解决方法可能是在异常发生时捕获异常并从头重新创建RecyclverView实例,以避免留下损坏的状态。
但是,如果可能的话,我希望更好地理解这个问题(也许从根源上解决它),而不是掩盖它。
这种细菌不容易繁殖,但一旦发生就会致命。
完整的堆栈跟踪:
W/dalvikvm( 7546): threadid=1: thread exiting with uncaught exception (group=0x41987d40)
E/AndroidRuntime( 7546): FATAL EXCEPTION: main
E/AndroidRuntime( 7546): Process: com.oblong.mezzedroid, PID: 7546
E/AndroidRuntime( 7546): java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:3
E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3382)
E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3340)
E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1810)
E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1306)
E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1269)
E/AndroidRuntime( 7546): at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:523)
E/AndroidRuntime( 7546): at org.liboid.recycler_view.RecyclerViewContainer$LiLinearLayoutManager.onLayoutChildren(RecyclerViewContainer.java:179)
E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1942)
E/AndroidRuntime( 7546): at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2237)
E/AndroidRuntime( 7546): at org.liboid.recycler_view.LiRecyclerView.onLayout(LiRecyclerView.java:30)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
E/AndroidRuntime( 7546): at com.oblong.mezzedroid.workspace.content.bins.BinsContainerLayout.onLayout(BinsContainerLayout.java:22)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
E/AndroidRuntime( 7546): at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
E/AndroidRuntime( 7546): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
E/AndroidRuntime( 7546): at android.view.View.layout(View.java:14946)
E/AndroidRuntime( 7546): at android.view.ViewGroup.layout(ViewGroup.java:4651)
E/AndroidRuntime( 7546): at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2132)
E/AndroidRuntime( 7546): at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1872)
E/AndroidRuntime( 7546): at andro
我有一个(可能)相关的问题-进入一个新的实例的活动与RecyclerView,但与一个较小的适配器触发这个崩溃为我。
RecyclerView.dispatchLayout()可以在调用mrecycle . clearoldpositions()之前尝试从废品中提取项目。结果是从公共池中提取位置高于适配器大小的项。
幸运的是,它只这样做,如果PredictiveAnimations启用,所以我的解决方案是子类GridLayoutManager(线性layoutmanager有同样的问题和“修复”),并覆盖supportpredictiveitemanimations()返回false:
/**
* No Predictive Animations GridLayoutManager
*/
private static class NpaGridLayoutManager extends GridLayoutManager {
/**
* 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;
}
public NpaGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public NpaGridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
}
public NpaGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
}
}
这是一个相当讨厌的虫子。
为了处理我的项目点击,我使用了RecyclerView的实现。OnItemTouchListener类似于在这个问题中找到的解决方案。
After many times of refreshing the RecyclerView's datasource and clicking an item, this IndexOutOfBoundsException would crash my application. When an item is clicked, the RecyclerView internally goes looking for the correct underlying view and gives back it position. Checking out the source code, I saw that there were some Tasks and Threads scheduled. To cut the story short, basically it's just some illegal state where two datasources are intermixed and not synchronized and the whole thing goes wild.
基于此,我删除了RecyclerView的实现。OnItemTouchListener,并简单地捕捉到适配器自己的ViewHolder上的点击:
public void onBindViewHolder (final BaseContentView holder, final int position) {
holder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick (View view) {
// do whatever you like here
}
});
}
这可能不是最好的解决方案,但目前没有崩溃。希望这将节省你一些时间:)。
我也有过这样的错误:
原因:我试图从异步任务更新一个回收器视图,同时试图获得旧的已删除的viewHolders;
代码:我在按下按钮时生成数据,逻辑如下
清除回收器视图中的最后一项
调用异步任务生成数据
更新Recycler视图和NotifyDataSetChanged
问题:每当我快速滚动之前生成我的数据我得到
不一致。无效的视图持有者适配器positionViewHolder
java.lang.IndexOutOfBoundsException:检测到不一致。无效项位置20(偏移量:2).state:3
解决方案:而不是在生成我的数据之前清除RecyclerView,我而是离开它,然后用新数据替换它,调用NotifyDatasetChanged,如下所示;
@Override
protected void onPostExecute(List<Objects> o) {
super.onPostExecute(o);
recyclerViewAdapter.setList(o);
mProgressBar.setVisibility(View.GONE);
mRecyclerView.setVisibility(View.VISIBLE);
}
林特给了我一个关于不一致性的建议:
我写了(onBindViewHolder()):
pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doStuff(position);
}
});
它必须被替换为:
pholder.mRlayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doStuff(pholder.getAdapterPosition());
}
});
在你的代码中运行这两个代码,然后运行Lint得到完整的解释!!
你只需要在OnPostExecute()上清除你的列表,而不是在执行拉取刷新时
// Setup refresh listener which triggers new data loading
swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
AsyncTask<String,Void,String> task = new get_listings();
task.execute(); // clear listing inside onPostExecute
}
});
我发现这种情况发生在您在拉出刷新期间滚动时,因为我在异步任务之前清除列表,导致java.lang.IndexOutOfBoundsException:检测到不一致。
swipeContainer.setRefreshing(false);
//TODO : This is very crucial , You need to clear before populating new items
listings.clear();
这样你就不会以前后矛盾结束
Sorry For late but perfect solution:
when you try to remove a specific item just call notifydatasetchange()
and get these item in bindviewholder and remove that item and add again to the last of list and then chek list position if this is last index then remove item.
basically the problem is come when you try to remove item from the center. if you remove item from last index then there have no more recycling and also your adpter count are mantine (this is critical point crash come here) and the crash is solved the code snippet below .
holder.itemLayout.setVisibility( View.GONE );//to hide temprory it show like you have removed item
Model current = list.get( position );
list.remove( current );
list.add( list.size(), current );//add agine to last index
if(position==list.size()-1){// remove from last index
list.remove( position );
}
我最近在使用新的Android架构组件时遇到了这种讨厌的堆栈跟踪。从本质上讲,我在ViewModel中有一个项目列表,由我的Fragment使用LiveData观察。当ViewModel发布数据的新值时,Fragment更新适配器,传入这些新的数据元素并通知适配器已经发生了变化。
不幸的是,当将新的数据元素传递给适配器时,我没有考虑到ViewModel和adapter都指向同一个对象引用!这意味着,如果我在ViewModel中更新数据并调用postValue(),就会有一个非常小的窗口,可以在其中更新数据,而适配器还没有收到通知!
我的修复是实例化一个新的副本的元素时,传递到适配器:
mList = new ArrayList<>(passedList);
有了这个超级简单的修复,您可以确保您的适配器数据不会改变,直到您的适配器被通知。
这是唯一的解决方案,对我来说,即使尝试了许多以上的解决方案。
1)。Intilization
CustomAdapter scrollStockAdapter =
new CustomAdapter(mActivity, new ArrayList<StockListModel>());
list.setAdapter(scrollStockAdapter);
scrollStockAdapter.updateList(stockListModels);
2)。在适配器中编写此方法
public void updateList(List<StockListModel> list) {
stockListModels.clear();
stockListModels.addAll(list);
notifyDataSetChanged();
}
stockListModels ->这个列表是你在适配器中使用的。
此错误发生在用户滚动时适配器中的列表清除,这使得项目holder的位置发生变化,在ui上列表和项目之间失去引用,在next发生错误
“notifyDataSetChanged”请求。
Fix:
检查您的更新列表方法。如果你这样做
mainList.clear();
...
mainList.add() or mainList.addAll()
...
notifyDataSetChanged();
===> Error occur
如何修复。为缓冲区处理创建新的列表对象,然后再次赋值给主列表
List res = new ArrayList();
…..
res.add(); //add item or modify list
….
mainList = res;
notifyDataSetChanged();
感谢Nhan Cao的帮助:)
在我的例子中,我得到了这个例外
java.lang.IndexOutOfBoundsException:检测到不一致。无效的视图持有者适配器positionViewHolder
以上答案在我的情况下都不适用。因为我正在更新/更改适配器中的现有项,但我使用了
myAdapter.notifyItemInserted(position)
相反,我应该用这个
myAdapter.notifyItemChanged(position)
注意:在插入项时应该使用notifyItemInserted(),在更新适配器中的项时应该使用notifyItemChanged()。
这个异常在API 19、21上引发(但不是新的)。在Kotlin协程中,我加载了数据(在后台线程),并在UI线程中添加并显示它们:
adapter.addItem(item)
适配器:
var list: MutableList<Item> = mutableListOf()
init {
this.setHasStableIds(true)
}
open fun addItem(item: Item) {
list.add(item)
notifyItemInserted(list.lastIndex)
}
由于某些原因,Android渲染速度不够快或其他原因,所以,我更新了RecyclerView的post方法中的列表(添加,删除,更新项目的事件):
view.recycler_view.post { adapter.addItem(item) }
此异常类似于“无法在滚动回调中调用此方法”。滚动回调可能在测量和布局传递期间运行,其中您不能更改theRecyclerView数据。任何可能改变RecyclerView结构或适配器内容的方法调用都应该延迟到下一帧。": Recyclerview -不能在滚动回调中调用此方法。
扩展LinearLayoutManager并捕获这个错误
public class NoCrashLinearLayoutManager extends LinearLayoutManager {
public NoCrashLinearLayoutManager(Context context) {
super(context);
}
public NoCrashLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public NoCrashLinearLayoutManager(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。设置setHasStableIds(true)以避免项目闪烁的适配器。
我在getItemId()中使用了一个可复制的字段(我的模型没有id字段):
override fun getItemId(position: Int): Long {
// Error-prone due to possibly duplicate name.
return contacts[position].name.hashCode().toLong()
}
getItemId()应该为每个项目返回一个唯一的id,所以解决方案是这样做:
override fun getItemId(position: Int): Long {
// Contact's phone is unique, so I use it instead.
return contacts[position].phone.hashCode().toLong()
}
在我的例子中,我使用的是DiffUtil。calculateDiff用于回收视图项,它在更新项时具有竞态条件。我在后台线程池中计算数据差异,并在UI线程中更新。如果有2个数据,并且第一个数据被设置为第二个差分计算和设置的时间之间的适配器,则会发生崩溃。
例如,假设我们有原始项目列表A、新数据B和新数据C:
B-diff =已计算(A, B)
C-diff =已计算(A, C)
集(B-diff)
集(C-diff)
C-diff实际上是不正确的,因为A已经更新,所以如果物品计数减少,recyclerview将在无效位置上动画。不只是崩溃,项目可能根本没有更新(或不必要的更新)由于不正确的差异索引。
更改为AsyncListDiffer解决了我的问题,因为如果第二个数据来得太早,它会删除第一个数据。
参考(它描述了问题,但给出了另一种解决方案(按顺序更新数据)):https://jonfhancock.com/get-threading-right-with-diffutil-423378e126d2
如果你使用setHasStableIds(true),你必须确保所有数据集项都有不同的id,否则可能会发生错误。
例如:
//This is our mock dataset which has FooItem type items
val dataset = listOf<FooItem>(items)
class FooItem {
//here your variables
private var id : Long = 0L
//you can handle different ids at high accuracy like;
fun getItemId() : Long {
if (id == 0L) {
id = abs(Random.nextLong(2, Long.MAX_VALUE))
}
return id
}
}
//Then go back your adapter and set adapter item id below like
override fun getItemId(position: Int) = dataset[position].getItemId()
通过这种方式,你将不需要关闭recyclerview动画并通知所有数据集。