RecyclerView 및 java.lang.IndexOutOfBoundsException : 불일치가 발견되었습니다. Samsung 장치에서 잘못된 뷰 홀더 어댑터 위치


253

삼성을 제외한 모든 기기에서 완벽하게 작동하는 재활용 뷰가 있습니다. 삼성에서는

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

난 당신이 어떻게 해결 ... 같은 문제를 gatting ....
Ashvin solanki

@ Владимир 확실한 답을 찾았습니까?
Alireza Noorali

내가 비동기 작업을 시작했기 때문에, 그리고 그들 중 하나는 예외를 얻을 수있는 사용자가 스크롤 아래로하고 그 사이 다른 완료 및 업데이트 어댑터 사용자가 다른 전에 완료 될 때 두 번째 작업은 데이터의 적은 양을 반환하기 때문에 내 경우에는, 그것은이었다
Vasif

답변:


196

이 문제는 RecyclerView다른 스레드에서 수정 된 데이터 로 인해 발생 합니다. 가장 좋은 방법은 모든 데이터 액세스를 확인하는 것입니다. 해결 방법이 줄 바꿈 LinearLayoutManager됩니다.

이전 답변

실제로 RecyclerView에 버그가 있었고 지원 23.1.1은 여전히 ​​수정되지 않았습니다.

이 문제를 해결하려면 역 추적 스택을 확인하십시오. Exception일부 클래스 중 하나에서이를 발견하면이 충돌을 건너 뛸 수 있습니다. 나를 위해, 나는를 생성 LinearLayoutManagerWrapper하고, 오버라이드 (override) 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
정확히 어디에 두었습니까? 어댑터 또는 활동?
Steve Kamau

를 확장하고 LinearLayoutManager이것을 재정의하십시오. 내 답변에 추가 할 것입니다.
sakiM

14
code.google.com/p/android/issues/detail?id=158046 answer # 12는 그렇게하지 않는다고 말했습니다.
Robert

음, 네 말이 맞아 내 응용 프로그램에서 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);

이것은 나를 위해 작동하는 것 같지만 왜 이것이 작동하는지 알 수 있습니까?
데니스 앤더슨

나도 고쳤다. 이것이이 충돌의 원인 일 수 있다고 예측 한 방법.
Rahul Rastogi

1
LinearLayoutManager 메서드의 기본 클래스 supportsPredictiveAnimations ()는 기본적으로 false를 반환합니다. 여기서 메소드를 재정 의하여 무엇을 얻습니까? public boolean supportsPredictiveItemAnimations() { return false; }
M. Hig

1
@ M.Hig에 대한 문서 LinearLayoutManager는 기본값이 거짓이라고 말하지만 그 진술은 거짓입니다 :-( 디 컴파일 된 코드 LinearLayoutManager에는 다음이 있습니다. ;}
Clyde

리사이클 뷰 어댑터를 업데이트하기 위해 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 () 대신 notifyitemrangeinserted를 사용하는 목적은 무엇입니까? @Bolling.
Ankur_009

@FilipLuch 왜 그런지 설명 할 수 있습니까?
Sreekanth Karumanaghat

3
@SreekanthKarumanaghat 물론, 이유를 설명하지 않은 이유는 없습니다. 기본적으로 그는 목록의 모든 항목을 지우고 다시 만듭니다. 검색 결과에서와 같이 매우 자주 동일한 항목을 얻거나 새로 고침이 완료되면 동일한 항목을 얻은 다음 모든 것을 다시 생성하게되므로 성능이 저하됩니다. 대신 DiffUtils를 사용하고 모든 항목이 아닌 변경 사항 만 업데이트하십시오. 매번 A에서 Z로가는 것과 같지만 거기에서 F 만 변경했습니다.
Filip Luchianenco

2
DiffUtil은 숨겨진 보물입니다. 공유해 주셔서 감사합니다!
Sileria

22

이 문제의 원인 은 다음과 같습니다.

  1. 항목 애니메이션을 사용할 때 Recycler의 내부 문제
  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을 사용할 수 없으며 오류는 알려진 특정 시나리오에서 정확하게 발생하므로 항목 애니메이터를 제거하지 않고 패치해야했습니다. 시도 및 캐치에 싸서
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 호출과 관련이 있습니다. [...]

Btw, 애니메이션과 성능을 죽이기 때문에 notifyDataSetChanged를 사용하지 않는 것이 좋습니다. 또한이 경우 특정 알림 이벤트를 사용하면 문제가 해결됩니다.

최신 버전의 지원 라이브러리에 여전히 문제가있는 경우 어댑터 내부의 호출 notifyXXX(특히 사용 notifyDataSetChanged) 을 검토하여 (약간 섬세하고 / 불분명 한) RecyclerView.Adapter계약을 준수하는지 확인하는 것이 좋습니다 . 또한 메인 스레드에서 해당 알림을 발행해야합니다.


16
실제로는 성능에 대해서는 귀하의 의견에 동의하지 않지만 notifyDataSetChanged ()는 notifyDataSetChanged ()를 사용하여 애니메이션을 만들지 않습니다 .a) RecyclerView.Adapter 객체에서 setHasStableIds (true)를 호출하고 b) 어댑터 내에서 getItemId를 재정 의하여 각 행마다 고유 한 긴 값을 확인하고 애니메이션을 확인하십시오.
PirateApp

@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

이해가되지 않습니다. 당신은 혼합 initialSize하고 list크기. 따라서 두 변종이 모두 잘못되었습니다.
CoolMind

나를 위해 그것은 작동 notifyItemRangeInserted(initialSize, list.size()-1);하지만 나는 그것을 얻지 못합니다. itemCount에 대해 삽입 된 크기를 1 씩 줄여야하는 이유는 무엇입니까?
plexus

7

이 문제가 발생하는 또 다른 이유는 잘못된 색인 (삽입 또는 제거가 발생하지 않은 색인)으로 이러한 메소드를 호출 할 때입니다.

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInserted

-notifyItemInserted

이 메소드에 대한 indexe 매개 변수를 점검하고 정확하고 정확한지 확인하십시오.


2
이것은 내 문제였다. 목록에 아무것도 추가하지 않으면 예외가 발생합니다.
Rasel

6

이 버그는 23.1.1에서 수정되지 않았지만 일반적인 해결 방법은 예외를 포착하는 것입니다.


18
어디서 정확히 잡아? 스택 추적의 유일한 코드는 기본 Android 코드입니다.
howettl

1
@saki_M 답변처럼 잡으십시오.
Renan Bandeira

Renan을 통해 실제로 작동합니까? 수정 사항을 잠시 테스트 했습니까? 버그는 가끔 발생하므로 시간이 지남에 따라 작동하는지 확인합니다.
Simon

이것은 실제로 작동하지만 광산의 일부 아동 견해는 부적절하게 남아 있습니다.
david

@david 일관성없는 결과는 무엇입니까?
Sreekanth Karumanaghat

4

이 문제는 다른 스레드에서 수정 된 RecyclerView 데이터로 인해 발생합니다.

스레딩을 하나의 문제로 확인할 수 있으며 문제가 발생하여 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

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

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가 포함 된 조각으로 Navigation 구성 요소를 사용합니다. 프래그먼트가 처음로드 될 때 내 목록이 잘 표시되었지만 탐색하고 돌아올 때이 오류가 발생했습니다.

프래그먼트 라이프 사이클을 탐색 할 때 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를 빠르게 스크롤 한 다음 뒤로 버튼 또는 위로 버튼으로 되돌아 갈 때만 발생한다는 것을 깨달았습니다. 그래서 위로 버튼을 넣고 아래 스 니펫을 뒤로 밀었습니다.

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

이 솔루션을 사용하면 새 Arraylist를 어댑터에로드하고 새 어댑터를 recyclerView에로드 한 다음 작업을 마칩니다.

누군가에게 도움이되기를 바랍니다.


0

실수로 "notifyItemInserted"를 두 번 호출했기 때문에이 오류가 발생했습니다.


0

내 경우에는 목록에 5000 개 이상의 항목이 있습니다. 내 문제는 리사이클 뷰를 스크롤 할 때 때때로 "myCustomAddItems"메소드가 목록을 변경하는 동안 "onBindViewHolder"가 호출된다는 것입니다.

내 솔루션은 데이터 목록을 변경하는 모든 메소드에 "동기화 (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 스레드에서 데이터 세트 만 업데이트하면 작동합니다.
Arron Cao
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.