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

Posted

技术标签:

【中文标题】RecyclerView 和 java.lang.IndexOutOfBoundsException:检测到不一致。三星设备中的无效视图支架适配器 positionViewHolder【英文标题】:RecyclerView and java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder in Samsung devices 【发布时间】:2015-10-23 21:36:20 【问题描述】:

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

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

我该如何解决这个问题?

【问题讨论】:

当你回来时,你的数据和你离开页面时一样吗? 我遇到了同样的问题,你是如何解决的...... @Владимир 你找到明确的答案了吗? 在我的情况下,这是因为我开始执行异步任务,并且当其中一个在另一个之前完成并且用户向下滚动同时另一个完成并更新适配器用户可以得到这样的异常,因为第二个任务返回的数据量较少 【参考方案1】:

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

【讨论】:

你把这个放在哪里?在适配器或活动上? 扩展 LinearLayoutManager 并覆盖它。我会在我的答案中添加。 code.google.com/p/android/issues/detail?id=158046 回答 #12 说不要那样做。 嗯,你是对的。在我的应用程序中消除所有潜在的非 UI 线程修改似乎很困难,我将仅将此作为一种解决方法。 就我而言,我在同一个线程上做。 mDataHolder.get().removeAll(mHiddenGenre); mAdapter.notifyItemRangeRemoved(mExpandButtonPosition, mHiddenGenre.size());【参考方案2】:

这是使用全新内容刷新数据的示例。 您可以轻松修改它以满足您的需求。 我通过调用解决了这个问题:

notifyItemRangeRemoved(0, previousContentSize);

之前:

notifyItemRangeInserted(0, newContentSize);

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

【讨论】:

这个解决方案对我有用我在这里尝试了很多答案但它们不起作用(我没有测试第一个解决方案) 问题是使用这些方法会产生这种不一致,即使在同一个线程上完成也是如此。 我不使用notifyItemRangeInserted,并且在某些三星设备上遇到了这个问题 这里很离题。作者没有使用notifyItemRangeInserted 这应该是公认的答案,它对我有用【参考方案3】:

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

【讨论】:

这似乎对我有用,但你能说出为什么会这样吗? 也为我修复了。您如何预测这可能是导致此次崩溃的原因。 LinearLayoutManager方法的基类supportsPredictiveAnimations()默认返回false。通过覆盖这里的方法,我们得到了什么? public boolean supportsPredictiveItemAnimations() return false; @M.Hig LinearLayoutManager 的文档说默认为 false,但该语句为 false :-( LinearLayoutManager 的反编译代码有: public boolean supportsPredictiveItemAnimations() return this .mPendingSavedState == null && this.mLastStackFromEnd == this.mStackFromEnd; 我使用 diff utils 来更新我的回收器视图适配器,这个答案修复了崩溃。非常感谢,亲爱的作者!【参考方案4】:

新答案: 对所有 RecyclerView 更新使用 DiffUtil。这将有助于提高性能和上述错误。 See Here

上一个答案: 这对我有用。关键是不要使用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());

【讨论】:

这是最彻底的解决方案,并有很好的解释。谢谢! 那么使用 notifyitemrangeinserted 而不是 notifydatasetchanged() 的目的是什么,@Bolling。 @FilipLuch 你能解释一下原因吗? @SreekanthKarumanaghat 当然,不知道为什么我没有解释原因。基本上他清除然后重新创建列表中的所有项目。就像在搜索结果中一样,您经常会得到相同的项目,或者当刷新完成后您会得到相同的项目,然后您最终会重新创建所有内容,这是对性能的浪费。改用 DiffUtils 并且只更新更改而不是所有项目。这就像每次从 A 到 Z,但你只改变了那里的 F。 DiffUtil 是一个隐藏的宝藏。感谢分享!【参考方案5】:

原因导致此问题:

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

解决方案:

-----------------解决方案 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 引起的问题。您需要确保以正确的方式使用通知方法。或者,使用 DiffUtil 以智能、简单和流畅的方式处理更改。 Using DiffUtil in Android RecyclerView

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

由于原因 #2,您需要检查对回收站列表的所有数据访问,并确保其他线程上没有修改。

【讨论】:

这在我的场景中有效,我不能使用 DiffUtil,因为我有用于回收器和适配器的自定义组件,并且错误恰好发生在已知的特定场景中,我只需要修补它而无需求助删除项目动画师,所以我只是把它包装在一个 try&catch【参考方案6】:

我也遇到过类似的问题。

以下错误代码有问题:

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【参考方案7】:

根据this issue 的说法,该问题已得到解决,并可能在 2015 年初附近的某个时间发布。A quote from that same thread:

具体与调用 notifyDataSetChanged 有关。 [...]

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

如果您在使用最新版本的支持库时仍有问题,我建议您在适配器内查看您对 notifyXXX 的调用(特别是您对 notifyDataSetChanged 的使用),以确保您遵守(有点微妙/晦涩)RecyclerView.Adapter 合同。还要确保在主线程上发出这些通知。

【讨论】:

不是真的,我同意你关于性能的部分,但 notifyDataSetChanged() 不会杀死动画,使用 notifyDataSetChanged() 进行动画处理,a) 在 RecyclerView.Adapter 对象上调用 setHasStableIds(true) 和 b)覆盖适配器内的 getItemId 以为每一行返回一个唯一的 long 值并检查它,动画确实有效 @PirateApp 您应该考虑将您的评论作为答案。我试过了,效果很好。 不是真的!仍然从 Google 控制台获取有关此问题的报告。设备当然是三星 - Samsung Galaxy J3(2017) (j3y17lte), Android 8.0 【参考方案8】:

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

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

【讨论】:

【参考方案9】:

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

【讨论】:

为什么是notifyItemRangeInserted(initialSize, mChannelItemList.size()-1); 而不是notifyItemRangeInserted(initialSize, list.size()); 无法理解。你混淆了initialSizelist 的大小。所以,你的两个变种都是错误的。 对我来说它适用于notifyItemRangeInserted(initialSize, list.size()-1);,但我不明白。为什么我必须将 itemCount 的插入大小减一?【参考方案10】:

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

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInserted

-notifyItemInserted

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

【讨论】:

这是我的问题。在列表中不添加任何内容时发生异常。 这个启发了我。我认为我的问题是两次调用adapter.clear()【参考方案11】:

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

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

我按照这个精彩解释中的建议解决了这个问题: https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2

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

希望这会有所帮助,因为这让我摸不着头脑!

【讨论】:

感谢您的链接!【参考方案12】:

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

【讨论】:

在哪里捕捉到它?堆栈跟踪中的唯一代码是原生 Android 代码。 像@saki_M 回答一样抓住它。 Renan 对你真的有用吗?您是否测试了一段时间的修复程序?这个错误只是偶尔发生,所以我只会看看它是否会随着时间的推移而起作用。 这确实有效,但我的一些子视图仍然不一致。 @david Inconsistently 仍然存在,这是什么后果?【参考方案13】:

这个问题是由于RecyclerView的数据在不同的地方修改造成的 线程

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

来自适配器的代码示例:

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]【参考方案14】:

就我而言,每次调用 notifyItemRemoved(0) 时,它都会崩溃。原来我设置了setHasStableIds(true)getItemId 我只是返回了项目位置。 我最终更新它以返回项目的hashCode() 或自定义的唯一 ID,从而解决了问题。

【讨论】:

【参考方案15】:

只有在以下情况下才会出现问题:

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

解决方案:

我通过仅在获得第一块数据并立即使用它初始化之后才创建适配器来解决这个问题。然后可以插入下一个块并毫无问题地调用notifyItemRangeInserted

【讨论】:

我不认为这是原因。我有许多带有空列表的适配器,然后添加了带有 notifyItemRangeInserted 的项目,但那里从未出现过此异常。【参考方案16】:

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

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

我当然要做的:

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

【讨论】:

【参考方案17】:

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

【讨论】:

或者直接通知是否删除了项目 我的模型使用了容器的引用,这就是为什么【参考方案18】:

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

【讨论】:

我的情况和你一样,你是怎么解决的?谢谢【参考方案19】:

通过将回收站视图更新为 last version 为我解决了问题

implementation "androidx.recyclerview:recyclerview:1.2.1"

【讨论】:

【参考方案20】:

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

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

【讨论】:

为什么notifyDataSetChanged 依赖于新数据?我认为它会刷新整个列表。【参考方案21】:

我遇到了同样的问题。

我的应用使用带有我的 recyclerView 片段的导航组件。我的列表在第一次加载片段时显示良好......但在导航离开并返回时出现此错误。

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

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

希望这可以帮助某人。

【讨论】:

【参考方案22】:

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

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

我希望这能帮助遇到同样问题的人...

【讨论】:

【参考方案23】:

感谢@Bolling 的想法 我已经实现支持以避免 List 中的任何可为空

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

【讨论】:

【参考方案24】:

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

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

【讨论】:

【参考方案25】:

我收到此错误是因为我错误地调用了一个方法来多次从我的 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);
    

【讨论】:

【参考方案26】:

我遇到了同样的问题,而且我听说这仅发生在三星手机中……但现实表明,这发生在很多品牌中。

经过测试,我意识到只有在快速滚动 RecyclerView 然后使用返回按钮或向上按钮返回时才会发生这种情况。所以我把里面的向上按钮和onBackpressed下面的sn-p:

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

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

希望对某人有所帮助

【讨论】:

【参考方案27】:

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

【讨论】:

【参考方案28】:

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

【讨论】:

【参考方案29】:

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

【讨论】:

【参考方案30】:

当我在 RecyclerView 滚动时更新数据时遇到了同样的问题。 我用以下解决方案修复了它:

    在更新数据之前停止滚动我们的 RecyclerView。 自定义布局管理器喜欢前面的答案。 使用 DiffUtils 确保更新数据正确无误。

【讨论】:

以上是关于RecyclerView 和 java.lang.IndexOutOfBoundsException:检测到不一致。三星设备中的无效视图支架适配器 positionViewHolder的主要内容,如果未能解决你的问题,请参考以下文章

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

java.lang.IllegalStateException:RecyclerView 在 Fragment 中没有 LayoutManager

Eclipse中使用recyclerview时出现Caused by: java.lang.NoClassDefFoundError: android.support.v7.recyclerview.

RecyclerView ViewPager java.lang.NullPointerException android.support.v7.widget.RecyclerView$ItemAni

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 in recyclerview

java.lang.IndexOutOfBoundsException :尝试从 RecyclerView 中删除 URL 损坏的项目