RecyclerView和java.lang.IndexOutOfBoundsException:检测到不一致。三星设备中的视图持有者适配器positionViewHolder无效

我有一个回收视图,在除三星以外的所有设备上都完美地工作。三星(Samsung)是我的首选

java.lang.IndexOutOfBoundsException:检测到不一致。无效的视图持有者适配器positionViewHolder

当我从另一个活动返回到带有回收器视图的片段时。

适配器代码:

public class FeedRecyclerAdapter extends RecyclerView.Adapter<FeedRecyclerAdapter.MovieViewHolder> {
public static final String getUserPhoto = APIConstants.BASE_URL + APIConstants.PICTURE_PATH_SMALL;
Movie[] mMovies = null;
Context mContext = null;
Activity mActivity = null;
LinearLayoutManager mManager = null;
private Bus uiBus = null;
int mCountOfLikes = 0;


//Constructor
public FeedRecyclerAdapter(Movie[] movies, Context context, Activity activity,
LinearLayoutManager manager) {
mContext = context;
mActivity = activity;
mMovies = movies;
mManager = manager;
uiBus = BusProvider.getUIBusInstance();
}


public void setMoviesAndNotify(Movie[] movies, boolean movieIgnored) {
mMovies = movies;
int firstItem = mManager.findFirstVisibleItemPosition();
View firstItemView = mManager.findViewByPosition(firstItem);
int topOffset = firstItemView.getTop();
notifyDataSetChanged();
if(movieIgnored) {
mManager.scrollToPositionWithOffset(firstItem - 1, topOffset);
} else {
mManager.scrollToPositionWithOffset(firstItem, topOffset);
}
}


// Create new views (called by layout manager)
@Override
public MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.feed_one_recommended_movie_layout, parent, false);


return new MovieViewHolder(view);
}


// Replaced contend of each view (called by layout manager)
@Override
public void onBindViewHolder(MovieViewHolder holder, int position) {
setLikes(holder, position);
setAddToCollection(holder, position);
setTitle(holder, position);
setIgnoreMovieInfo(holder, position);
setMovieInfo(holder, position);
setPosterAndTrailer(holder, position);
setDescription(holder, position);
setTags(holder, position);
}


// returns item count (called by layout manager)
@Override
public int getItemCount() {
return mMovies != null ? mMovies.length : 0;
}


private void setLikes(final MovieViewHolder holder, final int position) {
List<Reason> likes = new ArrayList<>();
for(Reason reason : mMovies[position].reasons) {
if(reason.title.equals("Liked this movie")) {
likes.add(reason);
}
}
mCountOfLikes = likes.size();
holder.likeButton.setText(mContext.getString(R.string.like)
+ Html.fromHtml(getCountOfLikesString(mCountOfLikes)));
final MovieRepo repo = MovieRepo.getInstance();
final int pos = position;
final MovieViewHolder viewHolder = holder;
holder.likeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mMovies[pos].isLiked) {
repo.unlikeMovie(AuthStore.getInstance()
.getAuthToken(), mMovies[pos].id, new Callback<Movie>() {
@Override
public void success(Movie movie, Response response) {
Drawable img = mContext.getResources().getDrawable(R.drawable.ic_like);
viewHolder.likeButton
.setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
if (--mCountOfLikes <= 0) {
viewHolder.likeButton.setText(mContext.getString(R.string.like));
} else {
viewHolder.likeButton
.setText(Html.fromHtml(mContext.getString(R.string.like)
+ getCountOfLikesString(mCountOfLikes)));
}
mMovies[pos].isLiked = false;
}


@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext.getApplicationContext(),
mContext.getString(R.string.cannot_like), Toast.LENGTH_LONG)
.show();
}
});
} else {
repo.likeMovie(AuthStore.getInstance()
.getAuthToken(), mMovies[pos].id, new Callback<Movie>() {
@Override
public void success(Movie movie, Response response) {
Drawable img = mContext.getResources().getDrawable(R.drawable.ic_liked_green);
viewHolder.likeButton
.setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
viewHolder.likeButton
.setText(Html.fromHtml(mContext.getString(R.string.like)
+ getCountOfLikesString(++mCountOfLikes)));
mMovies[pos].isLiked = true;
setComments(holder, position);
}


@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext,
mContext.getString(R.string.cannot_like), Toast.LENGTH_LONG).show();
}
});
}
}
});
}


private void setComments(final MovieViewHolder holder, final int position) {
holder.likeAndSaveButtonLayout.setVisibility(View.GONE);
holder.commentsLayout.setVisibility(View.VISIBLE);
holder.sendCommentButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (holder.commentsInputEdit.getText().length() > 0) {
CommentRepo repo = CommentRepo.getInstance();
repo.sendUserComment(AuthStore.getInstance().getAuthToken(), mMovies[position].id,
holder.commentsInputEdit.getText().toString(), new Callback<Void>() {
@Override
public void success(Void aVoid, Response response) {
Toast.makeText(mContext, mContext.getString(R.string.thanks_for_your_comment),
Toast.LENGTH_SHORT).show();
hideCommentsLayout(holder);
}


@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext, mContext.getString(R.string.cannot_add_comment),
Toast.LENGTH_LONG).show();
}
});
} else {
hideCommentsLayout(holder);
}
}
});
}


private void hideCommentsLayout(MovieViewHolder holder) {
holder.commentsLayout.setVisibility(View.GONE);
holder.likeAndSaveButtonLayout.setVisibility(View.VISIBLE);
}


private void setAddToCollection(final MovieViewHolder holder, int position) {
final int pos = position;
if(mMovies[position].isInWatchlist) {
holder.saveButton
.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_check_green, 0, 0, 0);
}
final CollectionRepo repo = CollectionRepo.getInstance();
holder.saveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(!mMovies[pos].isInWatchlist) {
repo.addMovieToCollection(AuthStore.getInstance().getAuthToken(), 0, mMovies[pos].id, new Callback<MovieCollection[]>() {
@Override
public void success(MovieCollection[] movieCollections, Response response) {
holder.saveButton
.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_check_green, 0, 0, 0);


mMovies[pos].isInWatchlist = true;
}


@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext, mContext.getString(R.string.movie_not_added_to_collection),
Toast.LENGTH_LONG).show();
}
});
} else {
repo.removeMovieFromCollection(AuthStore.getInstance().getAuthToken(), 0,
mMovies[pos].id, new Callback<MovieCollection[]>() {
@Override
public void success(MovieCollection[] movieCollections, Response response) {
holder.saveButton
.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_plus, 0, 0, 0);


mMovies[pos].isInWatchlist = false;
}


@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext,
mContext.getString(R.string.cannot_delete_movie_from_watchlist),
Toast.LENGTH_LONG).show();
}
});
}
}
});
}


private String getCountOfLikesString(int countOfLikes) {
String countOfLikesStr;
if(countOfLikes == 0) {
countOfLikesStr = "";
} else if(countOfLikes > 999) {
countOfLikesStr = " " + (countOfLikes/1000) + "K";
} else if (countOfLikes > 999999){
countOfLikesStr = " " + (countOfLikes/1000000) + "M";
} else {
countOfLikesStr = " " + String.valueOf(countOfLikes);
}
return "<small>" + countOfLikesStr + "</small>";
}


private void setTitle(MovieViewHolder holder, final int position) {
holder.movieTitleTextView.setText(mMovies[position].title);
holder.movieTitleTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MovieDetailActivity.openView(mContext, mMovies[position].id, true, false);
}
});
}


private void setIgnoreMovieInfo(MovieViewHolder holder, final int position) {
holder.ignoreMovie.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MovieRepo repo = MovieRepo.getInstance();
repo.hideMovie(AuthStore.getInstance().getAuthToken(), mMovies[position].id,
new Callback<Void>() {
@Override
public void success(Void aVoid, Response response) {
Movie[] newMovies = new Movie[mMovies.length - 1];
for (int i = 0, j = 0; j < mMovies.length; i++, j++) {
if (i != position) {
newMovies[i] = mMovies[j];
} else {
if (++j < mMovies.length) {
newMovies[i] = mMovies[j];
}
}
}
uiBus.post(new MoviesChangedEvent(newMovies));
setMoviesAndNotify(newMovies, true);
Toast.makeText(mContext, mContext.getString(R.string.movie_ignored),
Toast.LENGTH_SHORT).show();
}


@Override
public void failure(RetrofitError error) {
Toast.makeText(mContext, mContext.getString(R.string.movie_ignored_failed),
Toast.LENGTH_LONG).show();
}
});
}
});
}


private void setMovieInfo(MovieViewHolder holder, int position) {
String imdp = "IMDB: ";
String sources = "", date;
if(mMovies[position].showtimes != null && mMovies[position].showtimes.length > 0) {
int countOfSources = mMovies[position].showtimes.length;
for(int i = 0; i < countOfSources; i++) {
sources += mMovies[position].showtimes[i].name + ", ";
}
sources = sources.trim();
if(sources.charAt(sources.length() - 1) == ',') {
if(sources.length() > 1) {
sources = sources.substring(0, sources.length() - 2);
} else {
sources = "";
}
}
} else {
sources = "";
}
imdp += mMovies[position].imdbRating + " | ";
if(sources.isEmpty()) {
date = mMovies[position].releaseYear;
} else {
date = mMovies[position].releaseYear + " | ";
}


holder.movieInfoTextView.setText(imdp + date + sources);
}


private void setPosterAndTrailer(final MovieViewHolder holder, final int position) {
if (mMovies[position] != null && mMovies[position].posterPath != null
&& !mMovies[position].posterPath.isEmpty()) {
Picasso.with(mContext)
.load(mMovies[position].posterPath)
.error(mContext.getResources().getDrawable(R.drawable.noposter))
.into(holder.posterImageView);
} else {
holder.posterImageView.setImageResource(R.drawable.noposter);
}
holder.posterImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MovieDetailActivity.openView(mActivity, mMovies[position].id, false, false);
}
});
if(mMovies[position] != null && mMovies[position].trailerLink  != null
&& !mMovies[position].trailerLink.isEmpty()) {
holder.playTrailer.setVisibility(View.VISIBLE);
holder.playTrailer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MovieDetailActivity.openView(mActivity, mMovies[position].id, false, true);
}
});
}
}


private void setDescription(MovieViewHolder holder, int position) {
String text = mMovies[position].overview;
if(text == null || text.isEmpty()) {
holder.descriptionText.setText(mContext.getString(R.string.no_description));
} else if(text.length() > 200) {
text = text.substring(0, 196) + "...";
holder.descriptionText.setText(text);
} else {
holder.descriptionText.setText(text);
}
final int pos = position;
holder.descriptionText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MovieDetailActivity.openView(mActivity, mMovies[pos].id, false, false);
}
});
}


private void setTags(MovieViewHolder holder, int position) {
List<String> tags = Arrays.asList(mMovies[position].tags);
if(tags.size() > 0) {
CastAndTagsFeedAdapter adapter = new CastAndTagsFeedAdapter(tags,
mContext, ((FragmentActivity) mActivity).getSupportFragmentManager());
holder.tags.setItemMargin(10);
holder.tags.setAdapter(adapter);
} else {
holder.tags.setVisibility(View.GONE);
}
}


// class view holder that provide us a link for each element of list
public static class MovieViewHolder extends RecyclerView.ViewHolder {
TextView movieTitleTextView, movieInfoTextView, descriptionText, reasonsCountText;
TextView reasonText1, reasonAuthor1, reasonText2, reasonAuthor2;
EditText commentsInputEdit;
Button likeButton, saveButton, playTrailer, sendCommentButton;
ImageButton ignoreMovie;
ImageView posterImageView, userPicture1, userPicture2;
TwoWayView tags;
RelativeLayout mainReasonsLayout, firstReasonLayout, secondReasonLayout, reasonsListLayout;
RelativeLayout commentsLayout;
LinearLayout likeAndSaveButtonLayout;
ProgressBar progressBar;


public MovieViewHolder(View view) {
super(view);
movieTitleTextView = (TextView)view.findViewById(R.id.movie_title_text);
movieInfoTextView = (TextView)view.findViewById(R.id.movie_info_text);
descriptionText = (TextView)view.findViewById(R.id.text_description);
reasonsCountText = (TextView)view.findViewById(R.id.reason_count);
reasonText1 = (TextView)view.findViewById(R.id.reason_text_1);
reasonAuthor1 = (TextView)view.findViewById(R.id.author_1);
reasonText2 = (TextView)view.findViewById(R.id.reason_text_2);
reasonAuthor2 = (TextView)view.findViewById(R.id.author_2);
commentsInputEdit = (EditText)view.findViewById(R.id.comment_input);
likeButton = (Button)view.findViewById(R.id.like_button);
saveButton = (Button)view.findViewById(R.id.save_button);
playTrailer = (Button)view.findViewById(R.id.play_trailer_button);
sendCommentButton = (Button)view.findViewById(R.id.send_button);
ignoreMovie = (ImageButton)view.findViewById(R.id.ignore_movie_imagebutton);
posterImageView = (ImageView)view.findViewById(R.id.poster_image);
userPicture1 = (ImageView)view.findViewById(R.id.user_picture_1);
userPicture2 = (ImageView)view.findViewById(R.id.user_picture_2);
tags = (TwoWayView)view.findViewById(R.id.list_view_feed_tags);
mainReasonsLayout = (RelativeLayout)view.findViewById(R.id.reasons_main_layout);
firstReasonLayout = (RelativeLayout)view.findViewById(R.id.first_reason);
secondReasonLayout = (RelativeLayout)view.findViewById(R.id.second_reason);
reasonsListLayout = (RelativeLayout)view.findViewById(R.id.reasons_list);
commentsLayout = (RelativeLayout)view.findViewById(R.id.comments_layout);
likeAndSaveButtonLayout = (LinearLayout)view
.findViewById(R.id.like_and_save_buttons_layout);
progressBar = (ProgressBar)view.findViewById(R.id.centered_progress_bar);
}
}
}

例外:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{42319ed8 position=1 id=-1, oldPos=0, pLpos:0 scrap tmpDetached no parent}
at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:4166)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4297)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4278)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1947)
at android.support.v7.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:434)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1322)
at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:556)
at android.support.v7.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:171)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2627)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2971)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:562)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1626)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
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:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1626)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
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:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
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:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
07-30 12:48:22.688    9590-9590/com.Filmgrail.android.debug W/System.err? at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
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:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:15746)
at android.view.ViewGroup.layout(ViewGroup.java:4867)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2356)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2069)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1254)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6630)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:803)
at android.view.Choreographer.doCallbacks(Choreographer.java:603)
at android.view.Choreographer.doFrame(Choreographer.java:573)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:789)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5479)
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:1283)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
at dalvik.system.NativeStart.main(Native Method)

我该如何解决这个问题?

185354 次浏览

根据这个问题的说法,这个问题已经解决了,可能会在2015年初的某个时候发布。这句话来自同一条线索:

它与调用notifyDataSetChanged特别相关。[…]

顺便说一句,我强烈建议不要使用notifyDataSetChanged,因为它会杀死动画和性能。对于这种情况,使用特定的通知事件也可以解决这个问题。

如果你仍然对支持库的最新版本有问题,我建议检查你在适配器中对notifyXXX(特别是你对notifyDataSetChanged的使用)的调用,以确保你遵守了(有点微妙/模糊)RecyclerView.Adapter契约。还要确保在主线程中发出这些通知。

此问题是由RecyclerView数据在不同的线程中被修改引起的。最好的方法是检查所有的数据访问。一个变通方法是包装LinearLayoutManager

以前的回答

在RecyclerView中实际上有一个错误,支持23.1.1仍然没有修复。

为了解决问题,请注意回溯堆栈,如果我们可以在某个类中捕获这个Exception,它可能会跳过这个崩溃。对我来说,我创建了一个LinearLayoutManagerWrapper并覆盖了onLayoutChildren:

public class WrapContentLinearLayoutManager extends LinearLayoutManager {
//... constructor
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("TAG", "meet a IOOBE in RecyclerView");
}
}
}

然后将其设置为RecyclerView:

RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view);


recyclerView.setLayoutManager(new WrapContentLinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false));

实际上捕获了这个异常,似乎还没有任何副作用。

另外,如果你使用GridLayoutManagerStaggeredGridLayoutManager,你必须为它创建一个包装器。

注意:RecyclerView可能处于错误的内部状态。

如果您的数据变化很大,您可以使用

 mAdapter.notifyItemRangeChanged(0, yourData.size());

或者您的数据集中某些单项发生了变化,您可以使用

 mAdapter.notifyItemChanged(pos);

对于详细的方法使用,你可以参考医生,在某种程度上,尽量不要直接使用mAdapter.notifyDataSetChanged()

这个错误在23.1.1中仍然没有修复,但是常见的解决方法是捕获异常。

我也遇到过类似的问题。

错误代码中的问题如下:

int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize - 1, messageListHistory.size() -1);

解决方案:

int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize, messageListHistory.size() -prevSize);
这是一个用全新内容刷新数据的例子。 您可以轻松地修改它以满足您的需要。 在我的例子中,我通过调用:

来解决这个问题
notifyItemRangeRemoved(0, previousContentSize);

之前:

notifyItemRangeInserted(0, newContentSize);

这是正确的解决方案,AOSP项目成员也在这篇文章中提到过。

此错误可能是由于您的更改与所通知的内容不一致造成的。在我的例子中:

myList.set(position, newItem);
notifyItemInserted(position);

我必须做的是:

myList.add(position, newItem);
notifyItemInserted(position);

我得到了这个错误,因为我错误地调用了一个方法来多次从我的recyclerview中删除特定的行。我有一个方法:

void removeFriends() {
final int loc = data.indexOf(friendsView);
data.remove(friendsView);
notifyItemRemoved(loc);
}

我不小心调用了这个方法三次而不是一次,所以第二次loc是-1,当它试图删除它时给出了错误。这两个修复是为了确保方法只被调用一次,并且还添加了一个像这样的完整性检查:

void removeFriends() {
final int loc = data.indexOf(friendsView);
if (loc > -1) {
data.remove(friendsView);
notifyItemRemoved(loc);
}
}
我也有同样的问题。 这是由于我延迟通知适配器关于项目插入

但是ViewHolder试图在它的视图中重画一些数据,并且它启动了RecyclerView测量和重新计算子计数-在那一刻它崩溃了(项目列表和它的大小已经更新,但适配器还没有被通知)。

我曾经遇到过这个问题,我通过包装LayoutManager和禁用预测动画来解决这个问题。

这里有一个例子:

public class LinearLayoutManagerWrapper extends LinearLayoutManager {


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


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


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


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

并将其设置为RecyclerView:

RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManagerWrapper(context, LinearLayoutManager.VERTICAL, false);
< p >新回答: 对所有的RecyclerView更新使用DiffUtil。这将有助于提高性能和解决上面的错误。 在这里看到的 < / p > < p >之前的回答: 这对我很管用。关键是不要使用notifyDataSetChanged(),并以正确的顺序做正确的事情:

public void setItems(ArrayList<Article> newArticles) {
//get the current items
int currentSize = articles.size();
//remove the current items
articles.clear();
//add all the new items
articles.addAll(newArticles);
//tell the recycler view that all the old items are gone
notifyItemRangeRemoved(0, currentSize);
//tell the recycler view how many new items we added
notifyItemRangeInserted(0, newArticles.size());
}

此问题是由于RecyclerView数据修改在不同的 线程< / p >

可以确认线程作为一个问题,因为我遇到了这个问题,RxJava变得越来越流行:确保你在调用notify[whatever changed]时使用.observeOn(AndroidSchedulers.mainThread())

来自适配器的代码示例:

myAuxDataStructure.getChangeObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<AuxDataStructure>() {


[...]


@Override
public void onNext(AuxDataStructure o) {
[notify here]
}
});
在我的情况下,每次当我调用notifyItemRemoved(0)时,它崩溃了。原来,我设置了setHasStableIds(true),在getItemId中,我只是返回了项目的位置。 我最终更新它以返回项目的hashCode()或自定义的唯一id,这解决了问题

问题只发生在以下情况:

我用空列表创建了适配器。 然后插入项并调用notifyItemRangeInserted.

. 0

解决方案:

我解决了这个问题,方法是在获得第一块数据后才创建适配器,并立即对其进行初始化。然后可以插入下一个块,并调用notifyItemRangeInserted

我的问题是,即使我清除了包含回收器视图数据模型的数组列表,但我没有通知适配器该更改,因此它有来自以前模型的陈旧数据。这导致了视图持有者位置的混乱。若要修复此问题,请在再次更新之前通知适配器数据集已更改。

在我的情况下,问题是当新加载的数据量小于初始数据时,我使用了notifyDataSetChanged。 这个方法帮助了我:

adapter.notifyItemRangeChanged(0, newAmountOfData + 1);
adapter.notifyItemRangeRemoved(newAmountOfData + 1, previousAmountOfData);

当你为notifyItemChanged指定错误的位置时,会发生这种情况,

. notifyItemRangeInserted等

之前:(错误)

public void addData(List<ChannelItem> list) {
int initialSize = list.size();
mChannelItemList.addAll(list);
notifyItemRangeChanged(initialSize - 1, mChannelItemList.size());
}

之后:(正确)

 public void addData(List<ChannelItem> list) {
int initialSize = mChannelItemList.size();
mChannelItemList.addAll(list);
notifyItemRangeInserted(initialSize, mChannelItemList.size()-1); //Correct position
}

在我的情况下,我改变了之前在一个线程内的数据与mRecyclerView。post(new Runnable…),然后在UI线程中再次更改数据,这导致了不一致。

这个问题发生的另一个原因是当你用错误的索引(索引没有发生插入或删除)调用这些方法时

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInserted

-notifyItemInserted

检查这些方法的索引参数,确保它们是精确和正确的。

我也遇到了同样的问题,我读到这只发生在三星手机上…但现实表明,这发生在很多品牌。

经过测试,我意识到这只发生在你快速滚动RecyclerView,然后你回到后退按钮或向上按钮。所以我把里面的Up按钮和onBackpressed下面的片段:

someList = new ArrayList<>();
mainRecyclerViewAdapter = new MainRecyclerViewAdapter(this, someList, this);
recyclerViewMain.setAdapter(mainRecyclerViewAdapter);
finish();

在这个解决方案中,你只需要加载一个新的数组列表到适配器再加载一个新的适配器到recyclerView,然后你就完成了activity。

希望它能帮助到别人

在我的情况下,我得到这个问题,因为从服务器获取数据更新(我使用Firebase Firestore),而第一组数据正在由DiffUtil在后台处理,另一组数据更新来,并通过启动另一个DiffUtil导致并发问题。

简而言之,如果你在后台线程上使用DiffUtil,然后返回主线程将结果分派给RecylerView,那么当短时间内出现多个数据更新时,你就有可能得到这个错误。

我通过以下精彩解释中的建议解决了这个问题: https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2 < / p >

解决方案是在当前更新运行到Deque时推送更新。deque可以在当前更新完成后运行挂起的更新,从而处理所有后续更新,同时避免不一致错误!

希望这能有所帮助,因为这个问题让我挠头了!

我得到这个错误,因为我调用“notifyItemInserted”两次错误。

在我的情况下,我有超过5000项在列表中。 我的问题是,当滚动回收器视图时,有时“onBindViewHolder”被调用,而“myCustomAddItems”方法正在改变列表 我的解决方案是添加“synchronized (syncObject){}”到所有改变数据列表的方法。 这样在任何时间点上只有一个方法可以读取这个列表

在我的例子中,适配器数据发生了变化。我错误地使用了notifyItemInserted()用于这些更改。当我使用notifyItemChanged时,错误已经消失了。

原因导致此问题:

  1. 当启用项目动画时,回收器的内部问题
  2. 在另一个线程中对回收器数据的修改
  3. 以错误的方式调用notify方法

解决方案:

----------------- 解决方案1 ---------------

  • 捕获异常(不建议特别针对原因#3)

如下所示创建一个自定义LinearLayoutManager,并将其设置为ReyclerView

    public class CustomLinearLayoutManager extends LinearLayoutManager {


//Generate constructors


@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {


try {


super.onLayoutChildren(recycler, state);


} catch (IndexOutOfBoundsException e) {


Log.e(TAG, "Inconsistency detected");
}


}
}

然后设置RecyclerVIew布局管理器如下所示:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- 解决方案2 ---------------

  • 禁用物品动画(修复了导致原因#1的问题):

同样,创建一个自定义线性布局管理器,如下所示:

    public class CustomLinearLayoutManager extends LinearLayoutManager {


//Generate constructors


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

然后设置RecyclerVIew布局管理器如下所示:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- 解决方案3 ---------------

  • < >强此解决方案修复了由原因#3引起的问题。您需要确保以正确的方式使用notify方法。或者,使用DiffUtil以一种聪明、简单和流畅的方式处理更改。在Android RecyclerView中使用DiffUtil < / >

----------------- 解决方案4 ---------------

  • 对于原因#2,你需要检查所有数据访问回收器列表,并确保在另一个线程上没有修改

我也遇到了同样的问题。

我的应用程序使用导航组件的一个片段,其中包含我的recyclerView。我的列表显示良好的第一次片段加载…但是在导航离开和返回时发生了这个错误。

当导航离开片段生命周期只经过onDestroyView和返回时,它开始在onCreateView。但是,我的适配器在片段的onCreate中初始化,并且在返回时没有重新初始化。

修复方法是在onCreateView中初始化适配器。

希望这能帮助到一些人。

当我在列表中删除和更新项目时,我遇到了同样的问题… 经过几天的调查,我想我终于找到了解决办法

你需要做的是首先执行列表中的所有notifyItemChanged,然后才执行所有notifyItemRemoved 按降序排列

我希望这能帮助那些遇到同样问题的人……

我正在使用游标,所以我不能使用在流行答案中提出的DiffUtils。为了使它为我工作,我禁用动画时,列表不是空闲的。这是修复这个问题的扩展:

 fun RecyclerView.executeSafely(func : () -> Unit) {
if (scrollState != RecyclerView.SCROLL_STATE_IDLE) {
val animator = itemAnimator
itemAnimator = null
func()
itemAnimator = animator
} else {
func()
}
}

然后就可以像这样更新适配器了

list.executeSafely {
adapter.updateICursor(newCursor)
}
当我有更新数据时,我遇到了同样的问题,而RecyclerView正在滚动。 我用以下解决方案修复了它:

  1. 在更新数据之前停止滚动RecyclerView。
  2. 自定义布局管理器喜欢前面的答案。
  3. 使用DiffUtils确保更新数据是正确的。
多亏了@Bolling的主意 我有实现支持,以避免任何空值从列表

public void setList(ArrayList<ThisIsAdapterListObject> _newList) {
//get the current items
if (ThisIsAdapterList != null) {
int currentSize = ThisIsAdapterList.size();
ThisIsAdapterList.clear();
//tell the recycler view that all the old items are gone
notifyItemRangeRemoved(0, currentSize);
}


if (_newList != null) {
if (ThisIsAdapterList == null) {
ThisIsAdapterList = new ArrayList<ThisIsAdapterListObject>();
}
ThisIsAdapterList.addAll(_newList);
//tell the recycler view how many new items we added
notifyItemRangeInserted(0, _newList.size());
}
}

通过更新回收器视图到最后的版本为我解决了这个问题

implementation "androidx.recyclerview:recyclerview:1.2.1"