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


253

我有一个回收站视图,该视图可以在除三星之外的所有设备上完美运行。在三星上,我得到了

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)

我怎样才能解决这个问题?


返回时,您的数据与离开页面时的数据相同吗?
khusrav

我正在盖特同样的问题,如何ü解决....
Ashvin索兰奇

@Владимир找到明确的答案了吗?
Alireza Noorali

就我而言,这是因为我开始执行异步任务,并且当其中一个任务先于另一个任务完成并且用户向下滚动并且与此同时另一个任务完成并更新了适配器时,由于第二个任务返回的数据量较小,因此适配器用户可能会遇到此类异常
Vasif

Answers:


195

此问题是由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可能处于错误的内部状态。


1
你到底把它放在哪里?在适配器或活动上?
Steve Kamau,2015年

扩展一个LinearLayoutManager并覆盖它。我将在回答中补充一点。
sakiM 2015年

14
code.google.com/p/android/issues/detail?id=158046答案#12说不要这样做。
罗伯特

嗯,你是对的。在我的应用程序中似乎很难消除所有潜在的非UI线程修改,我将仅保留此变通方法。
sakiM

1
就我而言,我正在同一线程上执行。mDataHolder.get()。removeAll(mHiddenGenre); mAdapter.notifyItemRangeRemoved(mExpandButtonPosition,mHiddenGenre.size());
JehandadK

73

这是刷新具有全新内容的数据的示例。您可以轻松地对其进行修改以满足您的需求。我通过以下方式解决了这个问题:

notifyItemRangeRemoved(0, previousContentSize);

之前:

notifyItemRangeInserted(0, newContentSize);

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


2
该解决方案对我有用,我在这里尝试了很多答案,但是它们不起作用(我没有测试第一个解决方案)
AndroLife

问题是使用这些方法创建的不一致,即使在同一线程上完成也是如此。
JehandadK

我没有使用notifyItemRangeInserted某些三星设备,并且遇到了这个问题
user25

而且这里很不合时宜。作者未使用notifyItemRangeInserted
user25

1
谢谢!那帮助了我。
DmitryKanunnikoff

35

我曾经遇到过这个问题,所以我通过包装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);

这似乎对我有用,但是您能告诉我为什么这样做吗?
丹尼斯·安德森

也为我修复。您如何预测这可能是导致崩溃的原因。
拉胡尔·拉斯托吉

1
默认情况下,LinearLayoutManager方法的基类supportPredictiveAnimations()返回false。通过覆盖这里的方法,我们会得到什么? public boolean supportsPredictiveItemAnimations() { return false; }
M. Hig,

1
@ M.Hig的文档LinearLayoutManager说,默认值为false,但该语句为false :-(的反编译代码LinearLayoutManager具有:public booleansupportsPredictiveItemAnimations(){返回this.mPendingSavedState == null && this.mLastStackFromEnd == this.mStackFromEnd ;}
克莱德(Clyde)

我使用diff utils更新我的回收站视图适配器,并且此答案解决了崩溃问题。非常感谢,亲爱的作者!
Eugene P.

29

新的答案:将DiffUtil用于所有RecyclerView更新。这将有助于提高性能和上述错误。 看这里

以前的答案:这对我有用。关键是不使用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());
}

1
这是最详尽的解决方案,并有很好的解释。谢谢!
Sakiboy '17

那么使用notifyitemrangeinserted而不是@Bolling的notifydatasetchanged()的目的是什么。
Ankur_009

@FilipLuch你能解释为什么吗?
Sreekanth Karumanaghat

3
@SreekanthKarumanaghat当然,不知道为什么我没有解释原因。基本上,他清除然后重新创建列表中的所有项目。就像在搜索结果中一样,通常您会获得相同的项目,或者刷新后您会获得相同的项目,然后最终需要重新创建所有内容,这会浪费性能。请改用DiffUtils,并且仅更新更改,而不是所有项目。这就像每次从A到Z一样,但是您只更改了其中的F。
Filip Luchianenco

2
DiffUtil是隐藏的宝藏。感谢分享!
Sileria

22

导致此问题的原因

  1. 启用项目动画时,Recycler中的一个内部问题
  2. 在另一个线程中修改Recycler数据
  3. 以错误的方式调用通知方法

解:

-----------------解决方案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,您需要检查对回收者列表的所有数据访问,并确保对另一个线程没有任何修改。

在我的场景中这是可行的,我不能使用DiffUtil,因为我有用于回收站和适配器的自定义组件,并且该错误恰好发生在已知的特定场景中,我只需要对其进行修补而不用移除项目动画器,所以我只是将其包裹在try&catch中
RJFares

17

我有一个类似的问题。

错误代码如下:

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);

这对我来说很棒!不知道为什么我们不能简单地使用newList.size() - 1它。
waseefakhtar

15

根据这个问题,这个问题已经解决,并可能公布近2015年初一段时间,从同一线程A报价

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

顺便说一句,我强烈建议不要使用notifyDataSetChanged,因为它会破坏动画和性能。同样对于这种情况,使用特定的通知事件将解决此问题。

如果您仍然对支持库的最新版本有疑问,建议您检查一下对适配器内部的调用notifyXXX(特别是对的使用notifyDataSetChanged),以确保您遵守(有些微妙/晦涩)的RecyclerView.Adapter合同。另外,请确保在主线程上发出这些通知。


16
并非如此,我同意您关于性能的部分,但是notifyDataSetChanged()不会杀死动画,而是要使用notifyDataSetChanged()进行动画处理,a)在您的RecyclerView.Adapter对象上调用setHasStableIds(true),以及b)在Adapter内覆盖getItemId以返回a每行都有唯一的long值并将其签出,动画就可以正常工作
PirateApp 2015年

@PirateApp您应该考虑将您的评论作为答案。我已经尝试过了,并且工作正常。
mr5

不对!仍从Google控制台获取有关此问题的报告。当然,设备是三星-Samsung Galaxy J3(2017) (j3y17lte), Android 8.0
user25

10

我有同样的问题。这是因为我延迟了有关项目插入的适配器通知。

但是ViewHolder试图在视图中重画一些数据,并开始RecyclerView测量和重新计算子计数-那时它崩溃了(项目列表,它的大小已经更新,但是尚未通知适配器)。


8

当您为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 
 }

1
为什么notifyItemRangeInserted(initialSize, mChannelItemList.size()-1);notifyItemRangeInserted(initialSize, list.size());呢?
CoolMind

不知所措。你混了initialSizelist大小。因此,您的两个变体都是错误的。
CoolMind

对我来说,它可以工作,notifyItemRangeInserted(initialSize, list.size()-1);但我不明白。为什么我必须将itemCount的插入大小减小一倍?
plexus

7

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

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInserted

-notifyItemInserted

检查这些方法的索引参数,并确保它们正确无误。


2
这是我的问题。在列表中不添加任何内容时会发生异常。
拉塞尔

6

此错误在23.1.1中仍未修复,但是常见的解决方法是捕获异常。


18
确切地在哪里捕捉到它?堆栈跟踪中的唯一代码是本机Android代码。
howettl

1
就像@saki_M的答案一样抓住它。
Renan Bandeira

尽管这确实对您有效,但Renan是否对您有用?您是否测试了一段时间?该错误仅偶尔发生,因此我将仅查看它是否随着时间的推移而起作用。
西蒙(Simon)

这实际上是可行的,但是我的一些儿童观点仍然存在。
大卫

@david仍然不一致地导致了什么后果?
Sreekanth Karumanaghat

4

此问题是由在不同线程中修改的RecyclerView Data引起的

可以确认线程是一个问题,并且由于我遇到了这个问题,并且RxJava越来越流行:请确保.observeOn(AndroidSchedulers.mainThread())在每次调用时都在使用notify[whatever changed]

适配器的代码示例:

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

    [...]

    @Override
    public void onNext(AuxDataStructure o) {
        [notify here]
    }
});

我在调用DiffUtil.calculateDiff(diffUtilForecastItemChangesAnlayser(this.mWeatherForecatsItemWithMainAndWeathers,weatherForecastItems))时处于主线程上.dispatchUpdatesTo(this); 日志是明确的onThread:Thread [main,5,main]
Mathias Seguy Android2ee '18

4

就我而言,每次调用notifyItemRemoved(0)时,它都崩溃了。原来我已经设置好了,setHasStableIds(true)并且getItemId我刚刚返回了项目位置。我最终对其进行了更新,以返回项目的hashCode()或自定义的唯一ID,从而解决了该问题。


4

就我而言,由于从服务器获取数据更新(我正在使用Firebase Firestore)而出现此问题,并且当第一组数据由DiffUtil在后台处理时,另一组数据更新同时出现并导致并发问题通过启动另一个DiffUtil。

简而言之,如果您正在后台线程上使用DiffUtil,然后又回到主线程将结果分派到RecylerView,则当短时间内进行多个数据更新时,您就有机会得到此错误。

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

只是为了说明解决方案,就是在当前更新运行到Deque时推送更新。然后,双端队列可以在当前更新完成后运行挂起的更新,从而处理所有后续更新,但也避免了不一致错误!

希望这会有所帮助,因为这使我抓狂了!


感谢您的链接!
CoolMind

3

仅在以下情况下对我而言出现问题:

我用一个空列表创建了适配器。然后我插入了项目并打电话给notifyItemRangeInserted

解:

我仅在拥有第一批数据并立即对其进行初始化之后才创建适配器来解决此问题。然后可以notifyItemRangeInserted毫无问题地插入和调用下一个块。


我不认为这是原因。我有一个空列表,然后添加了许多项目的适配器notifyItemRangeInserted,但从来没有这样的例外存在。
CoolMind

3

我的问题是,即使我同时清除了两个包含回收器视图数据模型的数组列表,也没有将更改通知给适配器,因此它具有来自先前模型的陈旧数据。这引起了关于视图支架位置的困惑。要解决此问题,请在重新更新之前始终通知适配器该数据集已更改。


或只是通知物品是否已移走
Remario

我的模型使用了容器的引用,这就是为什么
Remario

3

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


1
我和你有同样的情况,你怎么解决的?谢谢
baderkhane 18/09/11

2

该错误可能是由于您的更改与您要通知的内容不一致而引起的。就我而言:

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

我当然要做的是:

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

2

就我而言,问题是当新加载的数据量小于初始数据时,我使用了notifyDataSetChanged。这种方法对我有帮助:

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

为什么要notifyDataSetChanged依赖新数据?我以为会刷新整个列表。
CoolMind

2

我遇到了同样的问题。

我的应用程序将导航组件与包含我的recyclerView的片段一起使用。第一次加载该片段时,我的列表显示得很好...但是导航离开并返回时,发生了此错误。

导航时,片段生命周期仅通过onDestroyView进行,返回时从onCreateView开始。但是,我的适配器已在片段的onCreate中初始化,并且返回时未重新初始化。

解决方法是在onCreateView中初始化适配器。

希望这可以帮助某人。


0

我收到此错误,是因为我错误地多次调用了一种从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);
    }
}

0

我遇到了同样的问题,并且我已经读到这仅发生在三星手机中...但是现实表明,这发生在许多品牌中。

经过测试,我意识到只有在快速滚动RecyclerView并随后使用“后退”按钮或“向上”按钮返回时,才会发生这种情况。因此,我将“向上”按钮放在里面,并在onBackpressed了以下代码段:

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

使用此解决方案,您只需将新的Arraylist加载到适配器,并将新的适配器加载到recyclerView,然后完成活动。

希望对别人有帮助


0

我收到此错误,是因为我错误地两次调用了“ notifyItemInserted”。


0

就我而言,列表中有5000多个项目。我的问题是,滚动回收视图时,有时会在“ myCustomAddItems”方法更改列表时调用“ onBindViewHolder”。

我的解决方案是将“ synchronized(syncObject){}”添加到更改数据列表的所有方法中。这样,在任何时候,只有一种方法可以读取此列表。


0

就我而言,适配器数据已更改。我错误地将notifyItemInserted()用于这些更改。当我使用notifyItemChanged时,错误消失了。


0

当我删除并更新列表中的项目时,我遇到了同样的问题。经过几天的调查,我认为我终于找到了解决方案。

您需要做的是先完成notifyItemChanged列表中的所有内容,然后再按降序进行所有操作notifyItemRemoved

我希望这可以帮助遇到相同问题的人们...


0

我使用的是游标,因此无法使用流行答案中建议的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)
}

0

如果多点触控后出现问题,您可以使用

android:splitMotionEvents="false" 

在布局文件中。


-1

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

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

或数据集中的某些单个项目发生更改,您可以使用

 mAdapter.notifyItemChanged(pos);

有关方法的详细用法,您可以参考doc,以某种方式尝试不直接使用mAdapter.notifyDataSetChanged()


2
使用notifyItemRangeChanged也会产生相同的崩溃。
lionelmessi

这适合某些情况。也许您同时在后台线程和UI线程中更新了数据集,这也会导致不一致。如果仅在UI线程中更新数据集,它将起作用。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.