RecyclerViewおよびjava.lang.IndexOutOfBoundsException:不整合が検出されました。SamsungデバイスのビューホルダーアダプターのpositionViewHolderが無効です


253

Samsung以外のすべてのデバイスで完全に機能するリサイクラービューがあります。サムスンでは

java.lang.IndexOutOfBoundsException:不整合が検出されました。ビューホルダーアダプターの位置が無効です

別のアクティビティからのリサイクラービューでフラグメントに戻るとき。

アダプターコード:

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 solanki

@Владимир決定的な答えは見つかりましたか?
Alireza Noorali

私の場合、それは私が非同期タスクを開始したためでした、そしてそれらの1つが別の前に完了し、ユーザーが下にスクロールし、その間に別のアダプターが完了して更新すると、2番目のタスクが返すデータの量が少ないため、このような例外が発生する可能性があります
Vasif

回答:


195

この問題はRecyclerView、別のスレッドで変更されたデータが原因で発生します。最善の方法は、すべてのデータアクセスをチェックすることです。そして、回避策は折り返しLinearLayoutManagerです。

前の答え

RecyclerViewには実際にバグがあり、サポート23.1.1はまだ修正されていません。

回避策として、バックトレーススタックがあることに注意してくださいException。あるクラスの1つでこれをキャッチできれば、このクラッシュがスキップされる可能性があります。私にとっては、を作成し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));

実際にはこの例外をキャッチし、まだ副作用はないようです。

また、使用する場合、GridLayoutManagerまたはStaggeredGridLayoutManagerそのラッパーを作成する必要がある場合。

通知:RecyclerViewが間違った内部状態にある可能性があります。


1
これを正確にどこに置くのですか?アダプタまたはアクティビティで?
スティーブカマウ2015年

これを拡張しLinearLayoutManagerてオーバーライドします。回答に追加します。
sakiM 2015年

14
code.google.com/p/android/issues/detail?id=158046回答#12では、これを行わないでください。
Robert

うーん、あなたは正しいです。アプリでUIスレッド以外の潜在的な変更をすべて拒否するのは難しいようですが、これは回避策としてのみ保持します。
sakiM 2016年

1
私の場合、私は同じスレッドでやっています。mDataHolder.get()。removeAll(mHiddenGenre); mAdapter.notifyItemRangeRemoved(mExpandButtonPosition、mHiddenGenre.size());
JehandadK 2017

73

これは、完全に新しいコンテンツでデータを更新する例です。ニーズに合わせて簡単に変更できます。私の場合、これを呼び出すことでこれを解決しました:

notifyItemRangeRemoved(0, previousContentSize);

前:

notifyItemRangeInserted(0, newContentSize);

これは正しい解決策であり、この投稿でもAOSPプロジェクトメンバーによって言及されています。


2
この解決策は私にとってはうまくいきました私はここでたくさんの答えを試しましたが、うまくいきません(私は最初の解決策をテストしていませんでした)
AndroLife

問題は、これらのメソッドを使用すると、同じスレッドで実行された場合でも、その不整合が生じることです。
JehandadK 2017

notifyItemRangeInserted一部のSamsungデバイスではこの問題を使用していません
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);

これは私にはうまくいくようですが、なぜこれがうまくいくのか分かりますか?
デニスアンダーソン

私も修正しました。これがこのクラッシュの原因であると予測した方法。
Rahul Rastogi

1
LinearLayoutManagerメソッドの基本クラスsupportsPredictiveAnimations()は、デフォルトでfalseを返します。ここでメソッドをオーバーライドすると何が得られますか? public boolean supportsPredictiveItemAnimations() { return false; }
M. Hig

1
@ M.HigのドキュメントでLinearLayoutManagerは、デフォルトはfalseと記載されていますが、そのステートメントはfalseです:-(の逆コンパイルされたコードにLinearLayoutManagerはこれがあります:public boolean supportsPredictiveItemAnimations(){return this.mPendingSavedState == null && this.mLastStackFromEnd == this.mStackFromEnd ;}
クライド

私はリサイクラービューアダプターの更新にdiff utilsを使用し、この回答でクラッシュが修正されました。どうもありがとう、親愛なる作者!
ユージーンP.

29

新しい回答:すべてのRecyclerView更新にDiffUtilを使用します。これは、パフォーマンスと上記のバグの両方に役立ちます。 こちらをご覧ください

以前の回答:これは私にとってはうまくいきました。重要なのは、使用せずに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

次に、notifydatasetchanged()、@ Bollingの代わりにnotifyitemrangeinsertedを使用する目的は何ですか。
Ankur_009 2018

@FilipLuch理由を説明できますか?
Sreekanth Karumanaghat

3
@SreekanthKarumanaghat確かに、なぜ私は理由を説明しなかったのか。基本的に彼はクリアしてから、リスト内のすべてのアイテムを再作成します。検索結果と同様に、多くの場合、同じアイテムを取得します。または、更新が完了すると、同じアイテムを取得し、すべてを再作成することになり、パフォーマンスの無駄になります。代わりにDiffUtilsを使用して、すべてのアイテムではなく変更のみを更新してください。毎回AからZに行くようなものですが、Fを変更しただけです。
Filip Luchianenco、

2
DiffUtilは隠された宝物です。共有してくれてありがとう!
Sileria

22

理由は、この問題を引き起こしました。

  1. アイテムのアニメーションが有効になっている場合のリサイクラーの内部の問題
  2. 別のスレッドでのリサイクラーデータの変更
  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 Layout Managerを次のように設定します。

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

-----------------解決策2 ---------------

  • アイテムのアニメーションを無効にします(理由1の原因となった場合の問題を修正します):

ここでも、次のようにカスタムの線形レイアウトマネージャーを作成します。

    public class CustomLinearLayoutManager extends LinearLayoutManager {

            //Generate constructors

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

次に、RecyclerVIew Layout Managerを次のように設定します。

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

-----------------解決策3 ---------------

  • 理由3が原因である場合、このソリューションは問題を修正します。正しい方法で通知メソッドを使用していることを確認する必要があります。または、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年の初め頃にリリースされた可能性があります同じスレッドからの引用

特に、notifyDataSetChangedの呼び出しに関連しています。[...]

ところで、私はnotifyDataSetChangedを使用しないことを強くお勧めします。アニメーションとパフォーマンスが低下するためです。この場合も、特定の通知イベントを使用することで問題を回避できます。

最近のバージョンのサポートライブラリで引き続き問題が発生notifyXXXする場合はnotifyDataSetChanged、アダプター内の(具体的にはの使用)への呼び出しを確認して、(ややデリケート/あいまいな)RecyclerView.Adapter契約を遵守していることを確認してください。また、メインスレッドでこれらの通知を発行してください。


16
実はそうではありませんが、パフォーマンスについてはあなたの部分に同意しますが、notifyDataSetChanged()はアニメーションを強制終了しません。a)RecyclerView.AdapterオブジェクトでsetHasStableIds(true)を呼び出し、b)アダプタ内のgetItemIdをオーバーライドしてaを返します。各行に一意の長い値を設定して確認すると、アニメーションが機能します
PirateApp 2015年

@PirateAppコメントとして回答することを検討してください。私はそれを試しました、そしてそれはうまくいきます。
mr5 2018年

違います!この問題に関するGoogleコンソールからのレポートを引き続き取得します。そしてもちろんデバイスはSamsungです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

ばかげた。あなたは混同initialSizeしてlistサイズを。したがって、両方のバリアントが間違っています。
CoolMind

私にとっては動作しますnotifyItemRangeInserted(initialSize, list.size()-1);が、取得できません。itemCountの挿入サイズを1つ減らす必要があるのはなぜですか?
神経叢

7

この問題が発生するもう1つの理由は、これらのメソッドを間違ったインデックス(挿入または削除が行われていないインデックス)で呼び出す場合です。

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInserted

-notifyItemInserted

これらのメソッドのインデックスパラメータをチェックし、それらが正確で正しいことを確認してください。


2
これが私の問題でした。リストに何も追加しないと例外が発生します。
Rasel、

6

このバグは23.1.1ではまだ修正されていませんが、一般的な回避策は例外をキャッチすることです。


18
正確にどこでそれをキャッチ?スタックトレースの唯一のコードは、ネイティブのAndroidコードです。
howettl 2016年

1
@saki_Mと同じようにキャッチします。
Renan Bandeira 2016年

これは実際にRenanを使用しても機能しますか?修正をしばらくテストしましたか?バグはたまにしか発生しないため、これが時間の経過とともに機能するかどうかのみを確認します。
Simon

これは実際に機能しますが、私の子供の見解の一部は一貫して残っています。
david

@david一貫性がなく、これの結果は何ですか?
Sreekanth Karumanaghat

4

この問題は、別のスレッドで変更されたRecyclerViewデータが原因で発生します

スレッド化を1つの問題として確認できます。この問題に遭遇し、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 2018

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 2017

私のモデルは、コンテナthatsの理由の参照使用しています
Remario

3

私の場合、以前はスレッド内でmRecyclerView.post(new Runnable ...)を使用してデータを変更し、その後UIスレッドでデータを変更していたため、矛盾が発生しました。


1
私はあなたと同じ状況ですが、どうやって解決しましたか?ありがとう
baderkhane

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

私は誤ってこのメソッドを1回ではなく3回呼び出していたため、2回目locは-1で、それを削除しようとしたときにエラーが発生しました。2つの修正は、メソッドが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」を2回呼び出していたため、このエラーが発生しました。


0

私の場合、リストに5000以上のアイテムがありました。私の問題は、リサイクラービューをスクロールするときに、「myCustomAddItems」メソッドがリストを変更しているときに「onBindViewHolder」が呼び出されることがあったことです。

私の解決策は、データリストを変更するすべてのメソッドに「synchronized(syncObject){}」を追加することでした。この方法では、いつでも1つのメソッドのみがこのリストを読み取ることができます。


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

メソッドの使用法の詳細については、ドキュメントを参照して、直接使用しないようにしてくださいmAdapter.notifyDataSetChanged()


2
を使用してnotifyItemRangeChangedも同じクラッシュが発生します。
lionelmessi

それはいくつかの状況に適しています。バックグラウンドスレッドとUIスレッドの両方でデータセットを更新した可能性があります。これも不整合の原因になります。UIスレッドでデータセットのみを更新する場合、それは機能します。
Arron Cao
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.