RecyclerView와 IndexOutOfBoundsException

2019. 2. 14. 09:51Programming/Android

반응형

기존에 릴리즈된 Android App을 유지보수하면서, RecyclerView에 아이템을 추가하는 코드가 때때로 IndexOutOfBoundsException로 인해 App Crash가 발생하는 경우가 발생했다. Android 자체를 너무 오랜만에 보다보니, RecyclerView에 대한 것부터 알아봐야했다. 자세한 내용을 정리하기엔 시간이 부족한데다가, 잘 정리된 글들이 많아서 이하의 링크로 대체한다.

RecyclerView, Android Developers
Android RecyclerView 사용하기, Taehwan 님
RecyclerView에 대한 고찰, Dudmy 님

스크롤이 발생했을 때 스크롤의 위치가 마지막일 경우 다음 검색결과를 불러온 뒤, notifyItemRangeChanged(int positionStart, int itemCount)를 호출하여 특정 부분을 갱신하고 있었다. 데이터가 많아지는 경우 렌더링을 최대한 효율적으로 처리하고 싶었던 것 같은데, 사용방법이 조금 이상했다. 검색조건이 변경되는 경우 검색결과가 초기화되는데, 이 때 positionStart의 값으로 0을, itemCount의 값으로 검색 결과의 갯수(20개로 제한되어 있었다)를 전달하고 있었다.

이 경우에 왜 IOOBE가 발생하는지는 좀 더 리서치가 필요하다. 하지만 리서치를 하지 않더라도 이전에 검색결과에 대한 ArrayList가 초기화되는 반면에, 이전에 렌더링됐던 데이터들을 전혀 고려하지 않았다는 사실은 이상했다. (예를들어서 기존에 40개의 검색결과를 ArrayList로 가지고 있을 때, 새로 20개의 검색결과를 받아와서 ArrayList의 내용을 대체하고 notifyItemRangeChanged(0, 20)을 호출한다. 이 때 이미 RecyclerView에 렌더링됐던 20~40에 해당하는 아이템들은 어떻게 처리되는가?)

처음에는 위의 내용을 파악하지 못해서, notifyDataSetChanged()를 호출하는 것으로 IOOBE가 발생하는 것을 회피할 수 있었다. 하지만 역시 검색결과가 많아지는 경우 성능에 대한 사항이 우려됐기에, 검색결과를 새로 받아와서 ArrayList를 초기화하는 경우에만 notifyDataSetChanged()를 호출하는 것으로 수정했다. (검색결과를 초기화하지 않고 다음 검색결과를 불러와서 ArrayList에 추가하는 경우, notifyItemRangeChanged를 호출해도 문제가 발생하지 않는다.)

RecyclerView의 최적화를 위하여 지원하는 메소드를 확인하고자 할 경우, 아래의 링크를 참조하도록 하자. RecyclerView에 대한 내용과 마찬가지로, 잘 정리된 글이 많아 링크로 대체한다.

RecyclerView, Android Developers
RecyclerView Adapter Refresh, GsBOB 님



public void notifyItemRangeChanged(int positionStart, int itemCount) {
    notifyItemRangeChanged(positionStart, itemCount, null);
}

public void notifyItemRangeChanged(int positionStart, int itemCount,
        @Nullable Object payload) {
    // since onItemRangeChanged() is implemented by the app, it could do anything, including
    // removing itself from {@link mObservers} - and that could cause problems if
    // an iterator is used on the ArrayList {@link mObservers}.
    // to avoid such problems, just march thru the list in the reverse order.
    for (int i = mObservers.size() - 1; i >= 0; i--) {
        mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
    }
}

위는 notifyItemRangeChanged의 내용이다. 일단 자세한 내용은 주말에 다시 리서칭해서 작성해야 할 듯 하다.

반응형