본문 바로가기
Android/View

[Android] RecyclerView의 원리 및 내부 동작

by 태크민 2025. 1. 23.

 

Android 앱을 개발할 때 리스트를 보여줘야한다면 가장 많이 사용되는 View는 RecyclerView일 것입니다. 

 

우리가 RecyclerView를 사용하면서 아래와 같이 스크롤을 했을 때 체크하지 않은 아이템에도 체크가 돼 있는 현상을 겪었던 적이 있을 것입니다. 

 

왜 이런 현상이 발생하는지 RecyclerView 내부 동작에 대해 깊게 알아보는 시간을 가져보도록 하겠습니다. 

 

 

🌀 RecyclerView 이전의 시대

RecyclerView가 등장하기 전, 안드로이드에선 어떤 리스트를 나타내는 뷰를 만들 때, ListView라는 것을 제공했습니다. 하지만 이 ListView에는 고질적인 문제가 존재했습니다.

 

대표적인 문제점은 아이템 리스트를 표현하는 ItemView의 갯수가 수 백개 이상으로 많아질 경우 ListView가 ItemView를 생성하는 속도가 매우 느려진다는 것이었습니다. 구글은 내부적으로 Adapter라는 것을 사용해서 당장 눈에 보이는 ItemView만 생성해라! 라는 대응책을 내놓았고, 눈에 안보이던 새로운 ItemView가 눈에 보여져야 할 때 ‘기존에 생성했던 ItemView를 그대로 재사용한다’ 라는 속임수를 사용했습니다. (ViewType이 동일할 경우에만)

 

시간이 지나고 ListView에 대한 개발자들의 요구 사항이 점점 커지고 Google은 ListView API가 너무 복잡해지고 있음을 느꼈습니다. 그리고 그로 인한 오작동을 일으키는 경우도 발생했습니다.

 

결국 ListView에 많은 기능을 추가하다 보니 안드로이드에 이미 존재하는 기능과 비슷한 것들이 중복으로 생겨나게 됩니다.

 

하지만 ListView가 가지고 있는 문제 중 최고는 바로 ItemView에 애니메이션을 구현하는 것이 너무 어렵다는 것이었습니다. ItemView를 제거하는 애니메이션을 구현했을 경우, ItemView를 하나씩 클릭하여 제거했을 경우에는 원하는 대로 작동하는 것 같았지만 여러 개의 ItemView를 클릭한 후 빠르게 스크롤하면 몇몇 ItemView가 아무런 데이터도 없는 상태로 비어있는 현상이 발생하게 됩니다. (ItemView의 애니메이션이 진행되는 동안 재사용 대상으로 선택됐기 때문)

 

애니메이션 효과가 완전히 끝나면 이 ItemView에 해당하는 객체가 메모리 상에서 제거되지만, 이 객체는 재사용되기로 약속된 객체이기 때문에 다시 사용되어야 합니다. 하지만 애니메이션으로 인해 메모리 상에서 제거된 경우, ListView가 새로운 데이터를 세팅할 재사용 ItemView를 메모리에서 찾지 못하게 되는데 이를 ListView의 붕괴 현상이라고 부릅니다.

 

현재는 ListView의 붕괴 현상을 해결하기 위해 안드로이드가 제공하는 ViewPropertyAnimator API를 사용하면 됩니다. 이것을 사용하면 애니메이션 효과가 진행 중인 ItemView는 작업이 진행중인 객체로 인식되어 재사용 대상으로 선택되지 않게 됩니다.

 

또 ListView를 구현할 때는 개발자가 직접 ItemView를 생성하는 로직과 데이터를 연결하는 로직을 분리해서 코드를 작성해야 했는데, 이 로직 분리 작업을 까먹는 경우도 많았고 이로 인해 앱의 성능을 악화시키는 원인이 되기도 했습니다.

 

이런 문제들이 발생하자 Google Android API 개발팀은 Google I/O 2016에서 기존 ListView 설계에 실수가 있었음을 인지하고 이 실수를 반복하지 않고자 RecyclerView를 개발했다고 발표하였습니다.

 

 

RecylerView의 주요 컴포넌트

  • RecyclerView.Adapter : 앱의 데이터셋에서 RecyclerView에 표시되는 아이템 뷰에 바인딩을 제공합니다. 어댑터는 RecyclerView의 각 아이템 뷰의 위치를 데이터 소스의 특정 위치에 연결하는 방법을 알고 있습니다.
  • RecyclerView.LayoutManager : RecyclerView내에 아이템을 배치합니다. 미리 정의된 여러 가지 레이아웃 매니저 중 하나를 사용하거나 커스텀 레이아웃 매니저를 구현하여 사용할 수 있습니다. 리니어 또는 그리드 레이아웃 매니저를 기본으로 사용할 수 있습니다.
  • RecyclerView.ItemAnimator : RecyclerView에는 기본 애니메이션이 함께 제공되며, 이를 오버라이드하고 필요에 따라 변경할 수 있습니다. 기본적으로 RecyclerView는 DefaultItemAnimator를 사용합니다.
  • RecyclerView.ViewHolder : RecyclerView와 함께 의무적으로 사용해야하며, 화면에 그리고 싶은 개별적인 아이템의 UI를 그릴 수 있도록 도와줍니다.

 

RecylerView의 내부 동작

RecyclerView는 컴포넌트 기반의 아키텍쳐 입니다.

 

이 3가지 컴포넌트가 적절하게 상호작용해야만 ItemView를 올바르게 RecyclerView안에 배치할 수 있는데, 각각의 컴포넌트를 하나씩 살펴보겠습니다.

 

📝 LayoutManager

먼저 LayoutManager는 무엇인지 살펴보겠습니다.

LayoutManager는 선형, 그리드, 엇갈린 그리드 모양으로 RecyclerView의 모습이 보여지도록 하는 역할을 가지고 있는 컴포넌트입니다.

RecyclerView는 자신이 어떤 모습으로 그려질지는 모릅니다. 이건 오직 LayoutManager가 담당합니다.

 

 

RecyclerView가 스크롤 될 때 LayoutManager는 어떤 일을 하게 될까요? 만약 유저가 리스트를 더 보기 위해 RecyclerView를 위로 스크롤 했다고 가정해봅시다. 이 경우, RecyclerView는 새로운 ItemView를 보여줘야한다는 것을 인식합니다. 하지만 새로운 ItemView를 어디에 배치할 지는 LayoutMananger가 알고 있습니다. 그러므로, 위 상황에서 RecyclerView는 LayoutManager에게 새로운 ItemView를 보여달라고 메시지를 전달합니다. 그럼 LayoutManager는 적절한 위치에 데이터가 연결된 ItemView를 연결합니다.

 

⛓ Adapter

RecyclerView에도 ListView와 동일하게 Adapter에 의존합니다. 하지만 ListView Adapter와의 차이점은 ViewHolder라는 것을 생성하는 작업을 담당한다는 것 입니다.

 

Adapter는 많은 작업을 처리합니다.

  • View와 ViewHolder를 만듭니다.
  • ViewHolder에 아이템을 바인드합니다.
  • RecyclerView에게 데이터셋이 변경 되었을 때 이를 알립니다.
  • 각각 데이터의 변경 이벤트를 처리합니다.
  • 아이템의 상호작용(ex: 클릭)을 처리합니다.
  • ItemView의 ViewType이 여러가지일 경우를 처리합니다.

 

ViewHolder의 Lifecycle

대부분의 개발자들은 ViewHolder를 작성하는 것에 시간을 많이 쓰기 때문에, ViewHolder의 수명주기를 아는 것은 매우 중요합니다.

🎊 탄생

ViewHolder의 탄생을 살펴봅시다. ViewHolder의 탄생에는 많은 분기가 발생합니다.

요구하는 ItemView가 캐시에 저장되어 있을 경우

  1. 유저가 스크롤 이벤트를 발생시켜서 RecyclerView가 LinearLayout에게 새로운 ItemView의 위치를 요구합니다.
  2. LayoutManager는 메시지를 받고 어떤 포지션에 새로운 ItemView가 배치되어야 하는지를 계산하고 이 위치를 다시 RecyclerView에게 알리는데, 위치를 보내주고 이 위치에 배치할 ItemView를 요청합니다.
  3. 이제 RecyclerView는 캐시에 해당 포지션에 배치되도록 지정된 ItemView가 있는지를 확인합니다. 만약 이 포지션에 배치되어야 하는 ItemView가 캐시되어 있을 경우 이 ItemView를 받아서 다시 LayoutManager에게 전달합니다.

요구하는 ItemView가 캐시에 없을 경우

 

  1. 그림의 4번 화살표에서 (4) Cache에 저장된 ItemView가 없다는 메시지가 날아오면 (5) RecyclerView는 Adapter에게 해당하는 ItemView의 ViewType을 getViewType 메소드를 통해 물어봅니다.
  2. (6) Adapter는 해당하는 ViewType을 알려주고 (7) RecyclerView는 이 ViewType을 가지고 Recycled Pool에 해당하는 ViewHolder가 있는지 물어봅니다.(getViewHolderByType 메소드)

여기서 사용하는 Recycled Pool은 공유되는 Recycled Pool일 수도 있고, 오직 이 RecyclerView에서만 사용하는 유일한 Recycled Pool일 수도 있습니다.

 

ViewHolder가 존재하지 않는 경우

 

(8) ViewType에 대한 ViewHolder가 없을 경우
(9) RecyclerView는 Adapter에 새로운 ViewHolder를 생성하라는 지시를 내리고

(10) Adapter는 생성한 ViewHolder를 RecyclerView에 전달합니다.

 

ViewHolder가 존재하는 경우

 

해당하는 ViewHolder가 존재하는 경우

(8) Recycled Pool은 ViewHolder를 RecyclerView에 전달하고

(9) Adapter에게 position과 ViewHolder를 넘겨주며 bind해달라는 명령을 내립니다.

(10) Adapter는 bind 작업이 완료된 ItemView를 RecyclerView에 전달하고

(11) RecyclerView는 이 ItemView를 LayoutManager에게 전달합니다.

 

마지막 단계

 

(1) 마지막으로 LayoutManager는 해당 위치에 ItemView를 배치하고 RecyclerView에게 배치가 완료되었다는 메시지를 보냅니다.

그리고 (2) RecyclerView는 Adapter에게 해당하는 position에 ItemView가 잘 배치되었다는 메시지를 보냅니다.

이렇게 우리는 ViewHolder의 탄생 과정을 알아보았습니다.

그렇다면 이번엔 재활용되는 경우의 Lifecycle을 살펴보겠습니다.

 

 

♻️ 재활용

이번엔 사용자가 스크롤을 위로 올려서 기존의 ItemView가 화면에서 사라지는 경우 어떤 일이 벌어지는지 살펴보겠습니다.

 

(1) LayoutManager가 화면에서 벗어난 ItemView의 position을 계산하고 RecyclerView에게 이를 알립니다. 그러면

(2) RecyclerView는 화면에서 ItemView를 제거한 후 Adapter에게 이를 알리게 되고, ItemView 안에 있는 것들의 캐싱을 해제할 수 있게 됩니다.

그리고 (3) RecyclerView는 Cache에게 제거되는 position의 ItemView가 캐시에 계속 남아있어도 되는 것인지를 Cache에게 물어봅니다.

 

만약, 이 ItemView가 사용된 지 오래된 ItemView라면, (3–1) Cache는 Recycled Pool에게 이 오래된 ItemView를 전달하고, (3–2) Recycled Pool은 Adapter에게 이 ItemView를 메모리에서 제거해도 된다는 메시지를 보냅니다.

 

그렇지 않고 계속 캐시에 저장할 필요가 있는 경우, (4) Cache에 계속 저장하라는 지시를 내리고, 이를 통해 나중에 LayoutManager가 해당 position에 대한 ItemView를 요청할 경우 Adapter를 거치지 않고 사용할 수 있게 됩니다.

 

RecycleredView Pool

여기까지 캐시와 RecyclerdViewPool을 탐색하며 RecyclerView가 ViewHolder를 찾는 과정, ViewHolder의 생성을 간략하게 알아봤습니다. 이제 RecycledViewPool에 대해 자세히 알아봅시다!

 

RecycledViewPool은 RecyclerView.Recycler의 inner class입니다. getRecycledView의 파라미터로 ViewType을 전달하면, ViewType에 맞는 ViewHolder를 return해준다는 것을 알 수 있고, ViewType마다 ViewHolder Pool을 가지고 있다는 것을 알 수 있습니다.

 

캐시에서 원하는 ViewHolder를 찾지 못한 경우 마지막으로 RecycledViewPool의 getRecycledView로 해당 ViewType에 해당하는 ViewHolder를 달라고 요청하는 것입니다.

또한 상수로 DEFAULT_MAX_SCRAP =5로 선언되어 있는 것은 ViewType별로 가지고 있는 pool의 기본 용량이 5개라는 것입니다

setMaxRecycledViews의 파라미터로 뷰타입과 pool이 가지고 있는 ViewHolder의 개수를 전달하면 pool의 용량을 늘리거나 줄일 수 있습니다.

 

이렇게 pool의 용량을 개발자가 직접 조절할 수 있다는 것은 매우 중요합니다.

만약 화면에 동일한 viewType을 가지는 아이템이 몇십개 존재하면 이들이 동시에 변경되어야 할땐 해당 viewType을 가지는 pool의 용량을 크게 설정하는 게 좋습니다.

ViewHolder를 많이 저장해두면 재사용할 수 있는 ViewHolder도 많아지기 때문입니다. 반면 화면에 딱 하나만 보여지는 ViewType이 있다면 용량을 1로 설정하면 메모리를 절약할 수 있습니다

 

또 하나 중요한 점은 RecycledViewPool가 public으로 설정된 class라는 것입니다. 즉 RecyclerView.RecycledViewPool() 처럼 RecycledViewPool 객체를 생성하여 해당 Pool을 '공유'할 수 있습니다.여러 RecyclerView들이 같은 Pool을 공유해 메모리를 절약할 수 있습니다.

 

 

dirty view

이처럼 pool에 있는 뷰들을 dirty view라고 부릅니다. dirty view는 pool에 들어올 때 뷰와 뷰타입만 남기고 potition, flags등의 상태는 초기화 되기 때문에 pool에 존재하는 dirty view들을 꺼내 쓰려면 데이터를 다시 바인딩해주어야 합니다

 

반면 pool이 아닌 캐시에 있는 view는 position,flags등의 상태를 그대로 가지고 있기 때문에 바인딩없이 그대로 재사용할 수 있습니다.

 

Cache

원하는 ViewHoler가 있는지 RecyclerdViewPool에서 찾기 전에 Cache를 먼저 방문한다고 했습니다.

 

Cache는 ViewHolder로 이루어진 리스트로, RecyclerdViewPool와 다르게 view type으로 ViewHolder를 구분하지 않습니다. 대신 "position"을 기준으로 탐색합니다. 따라서 캐시에 있는 ViewHolder는 데이터를 다시 바인딩 할 필요 없이 원래 위치해 있던 position에 그대로 재사용 될 수 있는 것입니다.
즉, 캐시에 저장된 ViewHolder가 화면에 표시됐을 때 어댑터의 onBindViewHolder()를 거치지 않고 그대로 리사이클러뷰에 표시합니다.

 

예를 들어 가장 위에 있던 position 5 아이템이 위로 스크롤 되어 화면에서 벗어난 후 다시 아래로 스크롤되어 화면에 보여질 때 position 5 에 해당하는 뷰홀더가 cache에 있었다면 5라는 position을 다시 바인딩할 필요없이 바로 재사용 할 수 있습니다.

 

즉, 뷰홀더가 어디에도 존재하지 않으면->새로 생성되고 바인딩됨->뷰홀더를 cache에서 찾았다면 view변경없이 바로 재활용->pool에서 찾았다면 바인딩 필요

 

ViewHolder 생성 시점 및 개수

우리는 RecyclerView가 ViewHolder를 재활용하는 과정에서 가장 상단에 있는 View가 사라지면 해당 ViewHolder가 스크롤시 하단에 나타나는 View에 바로 재활용된다고 생각합니다. 하지만 ViewHolder는 "바로" 재활용 되지 않습니다.

 

가장 처음 RecyclerView가 Adapter에 set되었을 때 호출되는 콜백함수를 보시면,

  1. RecyclerView가 화면에 onAttachedToRecyclerView가 호출되어 붙음
  2. 화면에 총 4개의 뷰홀더를 그리기 위한 onCreateViewHolder호출
  3. LayoutManager가 addView를 호출해 itemView를 position에 잘 붙인 후 RecyclerView에게 알리면 RecyclerView는 Adapter에게 onViewAttachedToWindow 호출

중요한건, 화면에 최초로 보이는 4개의 ViewHolder를 생성하고 난 후 스크롤을 내렸을 때입니다.

 

우리는 이때 바로 위에 있는 ViewHolder인 0번 홀더가 사라지고 5번째 itemView가 나타나면서 0번째 ViewHolder를 재사용할 것이기 때문에 바로 onCreateViewHolder를 건너뛰고 onBindViewHolder를 호출할 것이라 생각합니다.

 

하지만 5번째 itemView가 나타나며 또 ViewHolder를 생성합니다. 8번째 itemView가 나타날때까지 ViewHolder는 재활용되지 않고 계속 생성됩니다.

이는 RecyclerView가 사라진 ViewHolder를 '바로' 재활용하지 않기 때문에 나타나는 현상입니다.

즉,  ViewHolder는 한번에 보여지는 리스트 목록 개수에 약간의 버퍼를 추가한 개수만큼 생성이 됩니다.

 

계속 스크롤하다가 9번째 itemView가 만들어지는 순간 가장 먼저 detached되었던 0번째 ViewHolder가 onViewRecycled되어 나타납니다. onViewRecycled는 "재활용할 홀더를 가지고 왔음"을 알리는 메서드 입니다. 이후 9번째 itemView부터는 onCreateViewHolder가 호출되지 않고 detached되었던 ViewHolder들이 차례로 onViewRecycled되어 재사용됩니다.

 

 

정리하자면, 9번째 itemView가 나타났을 때 가장 먼저 사라졌던 0번째 홀더가 재사용됨을 알리는 onViewRecycled:0 이 호출되고 이 홀더가 9번째 홀더가되어 bind되는 것입니다.
바로 ViewHolder를 재사용한다는 의미의 콜백인 onViewRecycled가 호출되지 않는다는 것을 알 수 있습니다.

 


참고자료

https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2

 

RecyclerView Deep Dive with Google I/O 2016

RecyclerView를 RecyclerView ins and Outs — Google I/O 영상을 보며 내부 동작을 살펴봅니다.

medium.com

https://medium.com/hongbeomi-dev/%EB%B2%88%EC%97%AD-recyclerview%EC%9D%98-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-941a2827fa5a

 

[번역] — RecyclerView의 내부 동작

본 글을 Niharika Arora님의 글을 한국어로 번역한 글입니다. 원문 링크👇

medium.com

https://velog.io/@dabin/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0-RecyclerView%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83-2%ED%83%84ViewHolder%EC%88%98%EB%AA%85%EC%A3%BC%EA%B8%B0

 

[안드로이드 공식문서 파헤치기] RecyclerView의 모든 것! - 2편(ViewHolder수명주기)

(위에서 파란색 화살표를 제외하고 읽어주세요!) RecyclerView가 유저와의 상호작용의 결과, 스크롤 알림을 Layout Manager에게 알린다고 설명했습니다. 알림을 받은 Layout Manager는 몇 번째 위치(=position)

velog.io

https://onlyfor-me-blog.tistory.com/660

 

[Android] 리사이클러뷰가 있는 레이아웃의 성능 향상 - 1 -

이 포스팅은 아래의 미디엄 포스팅을 바탕으로 작성했다. https://medium.com/@abdennasser.abidi/improve-ui-performance-in-recyclerview-layout-part-1-d14cf1e23016 Improve UI Performance in RecyclerView Layout Part-1 RecyclerView is one of

onlyfor-me-blog.tistory.com