[안드로이드 공식문서 파헤치기] RecyclerView의 모든 것! - 4편(notifyDataSetChanged의 문제점)

dada·2022년 8월 18일
1
post-thumbnail

✅공부 배경

  • 안드로이드 프로젝트에서 RecyclerView를 사용할 때마다 ListAdapter,DiffUtil를 사용했습니다
  • 하지만 RecyclerView.AdapternotifyDataSetChanged는 모든 데이터를 변경하라는 의미이기 때문에 변경되지 않은 데이터도 변경시켜버리니 성능에 안좋다! 라는 것만 알고 습관적으로 ListAdapterDiffUtil을 사용했습니다🤣
  • 그래서, 이번 포스팅해선 RecyclerView의 관점에서notifyDataSetChanged의 정확한 문제점이 무엇인지 공부해보겠습니다

✅notifyDataSetChanged

  • notifyDataSetChanged는 RecyclerView.Adapter가 가진 public 메서드입니다. 주석을 읽어보면 등록된 관찰자에게 데이터 집합이 변경되었음을 알리는 메서드 라고 되어있습니다.
  • notifyChanged메서드를 보니 반복문을 통해 관찰자로부터 모든 item에 변경 이벤트가 생겼음을 알리고 있습니다
  • 이때, 데이터 변경 이벤트에는 2가지가 있습니다
      1. 항목 변경: 단일 항목이 데이터를 업데이트했지만 위치 변경이 발생하지 않은 경우입니다.
      1. 구조 변경: 데이터 세트 내에서 항목을 삽입, 제거 또는 이동하는 경우입니다.
  • notifyDataSetChanged는 데이터 집합이 변경된 내용을 지정하지 않으므로 관찰자는 모든 기존 항목과 구조가 더 이상 유효하지 않다고 가정합니다.(데이터 전부 바꼈고 아이템 삭제나 추가됐을테니 레이아웃 구조도 변경됐겠구나!라고 판단) LayoutManager는 표시되는 모든 보기를 완전히 다시 바인딩하고 ViewHolder를 생성해야 합니다.

✅RecyclerView관점에서 notifyDataSetChanged의 문제점

  • 이쯤에서 notifyDataSetChanged를 사용했을 때 RecyclerView의 내부 동작 과정에 어떤 변화가 있는지 궁금해졌습니다.

  • 로그를 보시면 RecyclerView의 item 수보다 살짝 많게 ViewHolder가 생성 된 후 onViewRecycled가 호출되며 ViewHolder가 재활용 되고 있음을 알립니다 이때 상단 검은 버튼을 클릭해서 notifyDataSetChanged를 호출했더니 onViewRecycled의 position이 -1이 13번 찍히다가 다시 ViewHolder를 생성하는 콜백인 onCreateViewHolder가 호출됩니다. 뷰홀더를 다시 만들고 있습니다!

  • 이때 position이 -1로 나타나는 이유는 무엇일까요? onViewRecycled의 position은 재활용하기 위해 가져온 itemView의 포지션을 나타냅니다. notifyDataSetChanged()에 의해 리사이클러뷰가 아이템뷰를 갱신하는 과정에서, 뷰홀더가 참조하는 itemView가 삭제되면 재활용할 itemView가 없고=position을 나타낼 수 없어서 NO_POSITION의 값인 -1을 리턴하게 되는 것입니다. -1이 13번 호출된 이유는 맨 처음 RecyclerView가 window에 attached되었을 때 ViewHolder를 create한 횟수가 13번 이었고, notifyDatasetChange가 호출된 후 모든 ViewHolder가 갱신되어야 하므로 처음처럼 13번 호출된 것 같습니다(제가 잘못 이해하고 있는 거라면 댓글 남겨주시면 감사하겠습니다🙇‍♀️)

  • 이러한 현상이 나타나는 이유는 notifyDataSetChanged가 호출되면 모든 뷰홀더는 더이상 유효하지 않은 뷰홀더라고 판단되어, 모두 pool로 들어가게 되는데 이때 pool의 공간이 충분하지 않으면(기본으로 pool의 사이즈는 5개 입니다) 몇몇 뷰홀더는 버려져서 GC에 의해 수거되고, 그 결과 새로운 뷰홀더를 생성하기 때문입니다.

  • 실제로 맨 처음 onCreateViewHolder를 호출하는 횟수를 카운팅해보았을 때 총 13번 호출되었으니 뷰홀더 인스턴스는 13개가 생성된 상태입니다. 이때 notifyDataSetChanged을 호출하면 총 13개의 뷰홀더가 다시 pool에 들어가려 하지만, 이미 pool은 꽉찬 상태입니다. 나머지 8개의 뷰홀더는 버려져서 GC에 의해 수거되고 새로운 뷰홀더 8개를 생성하기 위해 onCreateViewHolder가 8번 호출됩니다.

  • binding.rv.recycledViewPool.setMaxRecycledViews(0,8) 를 통해 pool의 사이즈를 8개로 늘리고 다시 테스트 해보면 notifyDataSetChanged가 호출된 후 총 5번의 onCreateViewHolder가 호출됨을 알 수 있습니다

  • 또한 모든 뷰홀더는 cache가 아닌 pool에 있던 dirty view이면서 새롭게 생성된 뷰홀더들이기 때문에 데이터를 다시 bind해줘야 하는 불필요한 작업까지 추가됩니다

  • 결과적으로 notifyDataSetChanged는 몇개의 항목이나 구조가 변경되었든 상관없이 초기의 뷰홀더 개수만큼 뷰홀더를 "다시" 생성하고 bind하니, 아이템의 수가 많은 경우 모든 아이템을 한번에 변경하느라 깜빡이는 현상까지 나타났던 것입니다.

  • 이런 이유로 공식문서에서 notifyDataSetChanged()를 "마지막 수단"으로 사용하라고 합니다!

profile
'왜?'라는 물음을 해결하며 마지막 개념까지 공부합니다✍

0개의 댓글