개요
- 이전에
RecyclerView
에 대한 글을 정리한 적이 있었다. 당시에는, RecyclerView의 등장 의의와 RecyclerView를 사용하는 경우에 구현해야 하는 구현부에 대한 내용을 중심으로 작성했었다. - 2022.12.22 - [IT/Android] - [Android] RecyclerView 이해하기
- 이외에
RecyclerView
에서 사용하는 Adapter를 중심으로 알아봤었다 - 2022.12.23 - [IT/Android] - [Android] RecyclerView 갱신의 효율성을 높이는 방법
- 그러나
RecyclerView
를 사용하면서, 근본적인RecyclerView
에 대한 이해를 필요로 하는 일이 늘어났고 특히,RecyclerView
의 내부 동작에 대한 이해가 필요로 하는 일이 늘어남에 따라 이번에는 RecyclerView의 내부 구조 동작을 중심으로 알아보고자 한다.
목적
RecyclerView
의 작동 방식 및 LifeCycle을 중심으로 알아볼 예정이다.- 이외에 ListView의 문제점을 비롯한 RecyclerView의 등장 배경과 정의, 성능 향상방법 등을 Google I/0 2016에 소개된 내용을 중심으로 알아볼 예정이다.
RecyclerView의 등장 의의
등장 이전: ListView
- 안드로이드에선
List
를 보여주기 위해ListView
를 사용했었다. 그러나,ListView
에는 고질적인 문제가 존재했다ListView
의ItemView
생성속도ItemView
애니메이션 구현의 어려움ItemView
재사용 여부 분기 처리- 변경된
Item Data
의Position
을 알 수 없음
ListView 문제점 - 1. ListView
의 ItemView
생성속도
- 대표적인 문제점으로, 아이템 리스트를 표현하는
ItemView
의 개수가 수백개 이상으로 많아지는 경우,ListView
가ItemView
를 생성하는 속도가 매우 느려진다는 것이었다.- 이에 따라 구글은 내부적으로
Adapter
라는 것을 사용해, 당장 눈에 보이는ItemView
만 생성하는 대응책을 내놓았고, 눈에 안보이던 새로운ItemView
가 눈에 보여져야 할 때 기존에 생성했던ItemView
를 그대로 재사용한다는속임수
를 사용했었다. (ViewType
이 동일한 경우에만) - →
ListView
가ItemView
를 재사용하지 않고 한번에 그리기 때문에 속도가 느리다 는 말은ListView
의Adapter
라는 재사용 메커니즘을 사용하지 않았을 때 적용되는 말이다.
→ 즉,ListView
도View
를 재활용 할 수 있다.
문제는, 이 재사용에 의해 후술할 애니메이션 처리라는 문제점이 생겨났다. - 이러한 문제들을 비롯해
ListView
에 대한 개발자들의 요구사항이 점점 커지고,ListView API
가 지나치게 복잡해짐에 따라 오작동을 일으키는 경우가 빈번해지기 시작했다
- 이에 따라 구글은 내부적으로
ListView 문제점 - 2. ItemView
애니메이션 구현의 어려움
- 또한
ListView
가 가진 가장 큰 문제는,ItemView
에 애니메이션을 구현하는 것이 너무 어렵다는 것ItemView
를 제거하는 애니메이션을 구현한 경우,ItemView
를 하나씩 클릭하여 제거했을 경우에는 원하는 대로 작동하는 것 같았지만 여러 개의ItemView
를 클릭한 후 빠르게 스크롤하면 몇몇ItemView
가 아무런 데이터도 없는 상태로 비어있는 현상이 발생하게 된다.
(ItemView
의 애니메이션이 진행되는 동안 재사용 대상으로 선택 됐기때문이다)
-
- 애니메이션 효과가 완전히 끝나면 이
ItemView
에 해당하는 객체가 메모리 상에서 제거되지만, 이 객체는 재사용되기로 약속된 객체이기 때문에 다시 사용되어야 한다. - 하지만 애니메이션으로 인해 메모리 상에서 제거된 경우,
ListView
가 새로운 데이터를 세팅할 재사용ItemView
를 메모리에서 찾지 못하게 되는데 이를 `ListView의 붕괴 현상`이라고 부른다. - → 현재는
ListView
의 붕괴 현상을 해결하기 위해 안드로이드가 제공하는ViewPropertyAnimator
API를 사용하면 된다. 이것을 사용하면 애니메이션 효과가 진행 중인ItemView
는 작업이 진행중인 객체로 인식되어 재사용 대상으로 선택되지 않게 된다.
- 애니메이션 효과가 완전히 끝나면 이
ListView 문제점 - 3. ItemView
재사용 여부 분기 처리
- 이외에
ListView
를 구현할 때는 개발자가 직접ItemView
를 생성하는 로직(onCreate)과 데이터를 연결하는 로직(onBind)을 분리해서 코드를 작성해야 했다.- 따라서 개발자들은 if 조건문을 통해 재사용될
ItemView
가 존재하면 바로 데이터를 재사용될ItemView
에 연결하고,
재사용될ItemView
가 존재하지 않으면(null
) 새로운ItemView
를 생성하는 코드를 추가해야 했다.
- 따라서 개발자들은 if 조건문을 통해 재사용될
- 문제는 이 로직 분리 작업을 까먹는 경우도 많았고 이로 인해
ListView
의ItemView
재사용 기능이 동작하지 않게 돼 앱의 성능을 악화시키는 원인이 되기도 했다.
ListView 문제점 - 4. 변경된 Item Data
의 Position
을 알 수 없음
ListView
에 보여질 데이터가 바뀐 경우, 기존의 ListView의 Adapter는 데이터가 변경되었다는 사실만 알 수 있을 뿐 구체적으로 목록에서 몇 번째 데이터가 변경되었는지 인식할 수 없었다.- 예를 들어, 사용자의 눈에 보이고 있는
ItemView
중3번째 ItemView
에 연결된 데이터가 어떠한 처리에 의해 바뀌게 되면Adapter
는 데이터가 바뀌었다는 사실만 알 뿐몇 번째 ItemView
에 연결된 데이터를 바꿔야 하는지는 알 수 없었다. - 따라서
3번째 ItemView
의 데이터를 바꾸기 위해 모든 ItemView에 데이터를 다시 연결해야 했다 - → 이러한 문제는 애니메이션 처리를 더욱 어렵게 만드는 원인이 되기도 했다.
- 예를 들어, 사용자의 눈에 보이고 있는
- 위와 같은 문제들이 발생하자 Google Android API 개발팀은 Google I/O 2016에서 기존
ListView
설계에 실수가 있었음을 인지하고 이 실수를 반복하지 않고자RecyclerView
를 개발했다고 발표하였다
ListView
는 한번에ItemView
를 그리기 어려웠음
→ 속임수 1) 눈에 보이는 ItemView만 생성
→ 속임수 2) 맨위에 있던 ItemView를 그려져야할 위치에 재사용
→ 문제 1) 개발자들 요구사항 증가
→ 문제 2) 다른 뷰에서 제공하는 비슷한 기능이 ListView에 추가되며 혼란 → 문제3)애니메이션 처리 문제
→ 문제 3) 재사용될 ItemView인지 분기처리 필요
→ 문제 4) Adapter는 변경된 Item Data의 Position 알 수 없음
⇒ RecyclerView 등장
RecyclerView의 정의
RecyclerView
는 안드로이드 프레임워크에서 제공하는 View 레이아웃 중 하나이다.RecyclerView
는 사용자가 관리하는 많은 수의 데이터 집합(Data Set)을 개별 아이템 단위로 구성해 화면에 출력하는뷰그룹
(ViewGroup)이며, 한 화면에 표시되기 힘든 많은 수의 데이터를 스크롤 가능한 리스트로 표시해주는위젯
이다RecycleView
는 Android Jetpack 구성 요소 중 하나이기 때문에안드로이드 프레임워크
에서 라이브러리 형식으로 제공하고 있다.
→ 개발자는 이 라이브러리를 가져다 사용해서 원하는 모양대로 응용하기만 하면 되는 것이다
RecyclerView의 성능 향상 방법
ViewHolder 패턴
을 통해ListView
의 성능을RecyclerView
에서 크게 향상할 수 있었다. 재활용하는View
들의 클래스를View 태그
또는Array
에 저장하고, 필요할 때 바로 가져와서 사용하는 방법으로 성능을 크게 향상하였다.ListView
에서 재활용되는View
를 해당 포지션에 맞게 가져오는 곳에서 성능을 향상할 수 있었다면,RecyclerView
는 가져온View
에 데이터를Bind
하는 경우 최적화할 수 있는 방법을 제공하고 있다.
RecyclerView의 동작 원리
RecyclerView
는 내부 아키텍처는 컴포넌트 기반의 아키텍처이며, 내부 아키텍처를 구성하는 컴포넌트는RecyclerView
,LayoutManager
,ItemAnimator
,Adapter
등이며, 이 중 아래의 3가지 컴포넌트가 가장 중요하다- 컴포넌트 기반 아키텍처: 이렇게
RecyclerView
,LayoutManager
,ItemAnimator
,Adapter
등의컴포넌트
가 서로 상호작용하여 하나의 목록형 레이아웃을 동작하게끔 만들기 때문에 컴포넌트 기반 아키텍처
- 컴포넌트 기반 아키텍처: 이렇게
- 이 3가지 컴포넌트가 적절하게 상호작용해야
ItemView
를 올바르게RecyclerView
안에 배치할 수 있다
1. LayoutManager
LayoutManager 정의
LayaoutManager
은RecyclerView
가 Item을 화면에 표시할 때,ItemVIew
들이RecyclerView
내부에서 배치되는 형태를 관리하는 컴포넌트이다LayoutManager
는선형
,그리드
,엇갈린 그리드
모양으로RecyclerView
의 모습이 보여지도록 하는 작업을 담당하는컴포넌트
이다Layoutmanager
는 각각LinearLayoutManager
(선형),GridlayoutManager
(그리드),StaggeredLayoutManager
(크기가 각각인 엇갈린 그리드)로 분류된다
- 즉,
RecyclerView
는 자신이 어떤 모습으로 그려질지는 모르며, 오직LayoutManager
가 담당하며,RecyclerView
의 모양을 만드는 작업을 책임지고,RecyclerView
모양에 따라ItemView
를 적절한 위치에 배치하는 작업을 담당한다.
LayoutManager가 하는일
- (1) 만약 유저가 리스트를 더 보기 위해
RecyclerView
를 위로 스크롤 했다고 가정해보는 경우, - (2)
RecyclerView
는 새로운ItemView
를 보여줘야한다는 것을 인식한다.
- (3) 하지만 새로운
ItemView
를 어디에 배치할 지는LayoutMananger
가 알고 있다. - (4) 그러므로, 위 상황에서
RecyclerView
는LayoutManager
에게 새로운 ItemView를 보여달라고 메시지를 전달한다.
(5) 그럼LayoutManager
는 적절한 위치에 데이터가 연결된 ItemView를 연결하게 된다.
2. Adapter
RecyclerView
에서도ListView
와 동일하게Adapter
에 의존한다.
(다른 adapter처럼 뷰와 데이터를 Bind)- 하지만
ListView Adapter
와의 차이점은ItemView
생성 외에도ViewHolder
라는 것을 생성하는 작업을 담당한다는 것이다.RecyclerView
는ViewHolder
를 기본적으로 사용하기 때문에,View
를 생성하지 않고,inflated
(XML의 레이아웃이 메모리에 객체화된)뷰
를 갖는ViewHolder
를 생성하게 된다.
→ViewHolder
가 생성되어 캐시가 쌓이면 필요할 때 재사용된다- 이 때 기억해야 할 점은
ViewHolder
는position
이 아닌itemViewType
에 의해 생성되는 것이다.
→ 이렇게 함으로써 재사용할 뷰를 더 쉽게 찾고 쉽게 추가할 수 있게된다.
Adapter가 하는 일
View
와ViewHolder
를 만든다ViewHolder
에 아이템을 바인드RecyclerView
에게 데이터셋이 변경 되었을 때 이를 알린다- 각각 데이터의 변경 이벤트를 처리
- 아이템의 상호작용(ex: 클릭)을 처리
ItemView
의ViewType
이 여러 가지일 경우를 처리- 재활용 실패에 대한 수습을 처리 (
onFailedToRecyclerView
)
onBindViewHolder()
onBindViewHolder()
는parameter
로ViewHolder
객체와position
값을 받고 Return 타입은 없다.position
값은Adapter
클래스 property 인 데이터셋의 특정 포지션이다.
예를 들어 데이터 셋이 배열 자료구조로 구성되었으면position
은 배열의 특정index
인 것이다.ViewHolder
객체는 데이터 셋의 특정position
에 저장되어 있는 아이템을 보여주기 위해 업데이트 되어야하는ViewHolder
이다
onBindViewHolder()
메소드는특정 position
의 데이터(item)을 보여주기 위해RecyclerView
가 호출하는 것이다.- 즉 호출하는 주체가
RecyclerView
인데RecyclerView
는 내부의tryBindViewHolderByDeadLine()
이라는 메서드 내부에서mAdapter.bindViewHolder()
를 호출한다.bindViewHolder()
에서onBindViewHolder()
가 호출된다
- 즉
RecyclerView
가 특정 상황이 발생했을 때 이를 알리기 위해bindViewHolder
를 호출하고, - 이 알림을 받았을때 해야하는 작업을 우리가
Adapter
내에onBindViewHolder()
메소드를 오버라이딩해 함수 내부에서는RecyclerView.ViewHolder.itemView
의 컨텐츠를 업데이트하는 작업이 실행된다.
→ 업데이트만하고 반환되는 작업이 없기에 onBindViewHolder()
는 콜백함수라는 걸 알 수 있다
ViewHolder의 LifeCycle
ViewHolder
의LifeCycle
는 대부분의 개발자들은ViewHolder
를 작성하는 것에 시간을 많이 쓰기 때문에, 아는 것이 매우 중요하다
ViewHolder Lifecycle - 1. Birth
1. 요구하는 ItemView
가 캐시에 저장되어 있을 경우
- (When) 유저가 스크롤 이벤트를 발생시
RecyclerView
가LayoutManager
에게 새로운ItemView
의 위치를 요구한다. - (1)
LayoutManager
는 메시지를 받고 → 어떤 포지션에 새로운ItemView
가 배치되어야 하는지를 계산하고
(2) 이 위치를 다시RecyclerView
에게 알리는데,
(3) 위치를 보내주고 이 위치에 배치할ItemView
를 요청한다.
(4)RecyclerView
는 캐시에 해당 포지션에 배치되도록 지정된ItemView
가 있는지를 확인한다
(recyclerview.getViewForPosition()
)
(5) 만약 이 포지션에 배치되어야 하는ItemView
가 캐시되어 있을 경우
→ 이ItemView
를 받아서 다시LayoutManager
에게 전달한다.
(Recyclerview
내부 동작 원리 상 일정 양의 ItemView를 캐시에 저장해 놓기 때문)
2. 요구하는 ItemView가 캐시에 없을 경우
- (4) Cache에 저장된
ItemView
가 없다는 메시지가 날아오면
(5)RecyclerView
는Adapter
에게(RecyclerView → Adapter
)
해당하는ItemView
의ViewType
을getViewType
메소드를 통해 물어본다 (adapter.getViewType()
)
(6)Adapter
는 해당하는ViewType
을 알려주고
(7)RecyclerView
는 이ViewType
을 가지고Recycled Pool
에서 해당하는 ViewHolder가 있는지 물어본다 (getViewHolderByType
메소드)- 여기서 사용하는
Recycled Pool
은 공유되는 Recycled Pool일 수도 있고,
오직 이RecyclerView
에서만 사용하는 유일한Recycled Pool
일 수도 있다.
- 여기서 사용하는
3. Recycled Pool에 ViewHolder가 존재하지 않는 경우
- (8)
ViewType
에 대한ViewHolder
가 없을 경우
(9)RecyclerView
는Adapter
에 새로운ViewHolder
를 생성하라는 지시를 내리고
(createViewHolder
)
(10)Adapter
는 생성한ViewHolder
를RecyclerView
에 전달
4. ViewHolder
가 존재하는 경우 (이미 생성된 경우)
Recycled Pool
에 해당ViewType
을 위한 해당하는ViewHolder
가 존재하는 경우
(8)Recycled Pool
은ViewHolder
를RecyclerView
에 전달하고
(9)RecyclerView
는Adapter
에게position
과ViewHolder
를 넘겨주며bind해달라는 명령
을 내린다.
(10)Adapter
는 bind 작업이 완료된ItemView
를RecyclerView
에 전달하고
(11)RecyclerView
는 이ItemView
를LayoutManager
에게 최종적으로 전달한다
5. 마지막 단계
- (12) 마지막으로 LayoutManager는 해당 위치에 ItemView를 배치하고 RecyclerView에게 배치가 완료되었다는 메시지를 보낸다 (addView())
(13) 그러면 RecyclerView는 Adapter에게 해당하는 position에 ItemView가 잘 배치되었다는 메시지를 보낸다 (onViewAttachedToWindow())
⇒ 결국 전체 과정을 살펴보면 Layout Manager
가 어떤 position에 배치될 ItemView
를 요청할 경우 해당 ItemView
가 Cache에 저장되어 있다면 Adapter
를 거치지 않고 바로 Layout Manager
에게 해당 ItemView
를 전달할 수 있음을 알 수 있다.
1) 레이아웃 매니저가 getViewForPosition 으로 view를 요청
2)
RecyclcerView
는 캐시에getViewForPosition
으로 확인. 있으면LayoutManager
에게 반환3) 캐시에 없으면
adapter
에게type
이 뭔지 물어보고Recycled Pool
에getViewHolderByType
으로 요청4)
Pool
에 있으면 반환, 없으면adapter
에게createViewHolder
로 아이템 생성5)
View
를 찾으면adapter
에서bindview
를 하고LayoutManager
에게 Return.6)
LayoutManager
는RecyclerView
에게addView
를 수행하고adapter
의onViewAttachedToWindow
가 호출됨.
캐시
와Recycled Pool
은RecyclerView
안에 이미 선언되어있음.
setItemViewCacheSize
함수로 캐시 사이즈 변경 가능.
ViewHolder Lifecycle - 2. Recycle (ItemView를 캐시에 저장하는 원리)
ViewHolder
의 Birth한 다음의 재활용 과정- 사용자가 스크롤을 위로 올려 기존의
ItemView
가 화면에서 사라지는 경우 일어나는 일
- (1)
LayoutManager
가 화면에서 벗어난ItemView
의 position을 계산하고RecyclerView
에게 이를 알린다 (removeAndRecycleView
)그러면
(2)RecyclerView
는 화면에서ItemView
를 제거한 후Adapter
에게 이를 알리게 되고, (onViewDetachedFromWindow()
) →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
를 거치지 않고 사용할 수 있다.
ViewHolder LifeCycle - 3. Fancy Reserves
LayoutManager
가 다시 레이아웃을 계산하는 도중adapter
가 변경되는 경우이다.- 몇몇 View가 더 이상 사용하지 않게 되었을 경우의 상황은 아래 그림과 같다.
- (1)
RecyclerView
가LayoutManager
에게 사용되지 않는ItemView
가 있음을 알린 후,
(2) 사라지는ItemView
들을 다시ViewGroup
에 추가한다.
그런 다음, (3)LayoutManager
에서 이ItemView
들을 숨긴다.
이 때 (4)RecyclerView
는 이ItemView
들에 대해ItemAnimator
에게 애니메이션 처리를 요구하게 되고, 애니메이션 처리가 끝나면 (onAnimationFinished
)
(5)Adapter
의onViewDetachedFromWindow
를 호출해 제거가 완료되었음을 알린다.
그 후 (6)Cache
와Recycled Pool
에게 캐시를 업데이트하고 제거된ItemView
를 재활용하라고 지시한다.
ViewHolder Lifecycle - 4. Death
ViewHolder
를 잃는 경우이다- 이 경우 성능 문제가 발생하기 때문에 이는 매우 중요하다
Death Case #1
- 1번째
ViewHolder
소멸은ItemAnimator
를 사용하지 않고 애니메이션을 구현하는 경우 발생가능하다- (1)
LayoutManager
는ItemView
가 더 이상 필요하지 않고 이ItemView
가transientState
에 빠진 경우(애니메이션이 진행 중인 상태, 예를 들어Fading out
) 이를RecyclerView
에게 알리는데, - (2)
RecyclerView
는 이ItemView
가 유효한지 확인한 후, 유효하지 않다면 이를Recycled Pool
에 알린다. - (3)
Recycled Pool
에서 다시 한 번transientState
인지 확인하고, 해당 상태라면 재활용에 사용할 수 없으므로Adapter
의onFailedToRecycle()
을 호출한다. - → 여기서 우리는 임의로 Adapter의 onFailedToRecycle의 리턴값을 true로 재구현해 Recycled Pool에게 “날 믿고 재활용해라” 라는 메시지를 전달 할 수도 있다.
- 하지만 기본적으로
Adapter
의onFailedToRecycle()
을 호출하면false
를 리턴하도록 구현되어 있어서ViewHolder
는 파괴된다. - → 이는 우리가 원치 않는 결과이며, ItemAnimator를 사용하지 않고 애니메이션을 구현할 경우 해당 상황에 빠질 수 있다.
- →
ItemAnimator
를 사용하면 올바른 Lifecycle 이벤트를 수신하여ItemView
를 재활용할 수 있게 도와준다.
- (1)
Death Case #2
- (1) `RecyclerView`에서 `ItemView`에 대한 처리를 마쳤고 이를 `Recycled Pool`에게 알린다.
- Recycled Pool은 제한된 크기를 가지고 있기 때문에 해당하는 `ViewHolder`에 대해 더 이상 남는 자리가 없는 경우 `ViewHolder`가 제거된다.
- 일반적으로 이는 다음과 같은 경우에 발생한다
1. 많은 `ViewHolder`들이 같은 타입인 경우 캐싱이 되지않아 `Recycled Pool`로 넘어가서 제거되었다
2. 모든 `ItemView`들이 애니메이션이 존재할 때, `notifyItemRangeChanged(0, getItemCount())가` 호출되면서, 애니메이션이 끝나고 이 `ItemView`들을 `Recycled Pool`에 다시 추가하려고하는데,
이 때, R`ecycled Pool`은 이렇게 많은 아이템이 필요하지 않다고 판단하고 제거된다. - 이름 방지하기 위해선 다음 방법을 사용할 수 있다.
- 1. 조금 더 세밀하게 adapter를 업데이트하기 (ex:
notifyItemChanged(3)
) - 2.
pool.setMaxRecycledViews(type, count)
를 통해 pool의 크기를 늘리기
- 1. 조금 더 세밀하게 adapter를 업데이트하기 (ex:
RecycledViewPool
- 위에서 캐시와
Recycled
Pool을 탐색하며,RecyclerView
가ViewHolder
를 찾는 과정과,ViewHolder
의 생성을 알 수 있었다. 그렇다면,RecycledViewPool
이란 어떤 것일까?
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
public void setMaxRecycledViews(int viewType, int max) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mMaxScrap = max;
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}
public int getRecycledViewCount(int viewType) {
return getScrapDataForType(viewType).mScrapHeap.size();
}
@Nullable
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
for (int i = scrapHeap.size() - 1; i >= 0; i--) {
if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
return scrapHeap.remove(i);
}
}
}
return null;
}
RecycledViewPool
은RecyclerView.Recycler
의inner class
이다.getRecycledView
의parameter
로ViewType
을 전달하면,ViewType
에 맞는ViewHolder
를 return해준다는 것을 알 수 있고,ViewType
마다ViewHolder Pool
을 가지고 있다는 것을 알 수 있다.- 즉 캐시에서 원하는
ViewHolder
를 찾지 못한 경우 마지막으로RecycledViewPool
의getRecycledView
로 해당ViewType
에 해당하는ViewHolder
를 달라고 요청하는 것이다 - 또한 상수로
DEFAULT_MAX_SCRAP =5
로 선언되어 있는 것은ViewType
별로 가지고 있는pool
의 기본 용량이 5개라는 것이다 setMaxRecycledViews
의parameter
로ViewType
과pool
이 가지고 있는ViewHolder
의 개수를 전달하면pool
의 용량을 늘리거나 줄일 수 있다.- 이렇게
pool
의 용량을 개발자가 직접 조절할 수 있다는 것은 매우 중요한데,
만약 화면에 동일한viewType
을 가지는 아이템이 몇십개 존재하면 이들이 동시에 변경되어야 할땐 해당viewType
을 가지는pool
의 용량을 크게 설정하는 게 좋다.ViewHolder
를 많이 저장해두면 재사용할 수 있는 ViewHolder도 많아지기 때문이다.
반면 화면에 딱 하나만 보여지는ViewType
이 있다면 용량을 1로 설정하면 메모리를 절약할 수 있다
- 이렇게
- 또 하나 중요한 점은
RecycledViewPool
가public
으로 설정된class
라는 것이다. 즉RecyclerView.RecycledViewPool()
처럼RecycledViewPool
객체를 생성해 해당 Pool을 '공유'할 수 있다.
→ 즉여러 RecyclerView
들이같은 Pool
을 공유해 메모리를 절약할 수 있다.
Dirty View
- 위처럼
pool
에 있는View
들을dirty view
라고 부른다. dirty view
는pool
에 들어올 때View
와ViewType
만 남기고potition
,flags
등의 상태는 초기화 되기 때문에pool
에 존재하는dirty view
들을 꺼내 쓰려면 데이터를 다시 바인딩해주어야 한다.- 반면
pool
이 아닌캐시
에 있는view
는position
,flags
등의 상태를 그대로 가지고 있기 때문에 바인딩없이 그대로 재사용할 수 있다.
Cache
- 원하는
ViewHoler
가 있는지RecyclerdViewPool
에서 찾기 전에Cache
를 먼저 방문한다. Cache
는ViewHolder
로 이루어진리스트
로,RecyclerdViewPool
와 다르게viewType
으로ViewHolder
를 구분하지 않는다.- 대신 "
position
"을 기준으로 탐색한다. 따라서캐시에 있는 ViewHolder
는 데이터를 다시 바인딩 할 필요 없이 원래 위치해 있던position
에 그대로 재사용 될 수 있는 것이다 - 예를 들어 가장 위에 있던
position 5
아이템이 위로 스크롤 되어 화면에서 벗어난 후 다시 아래로 스크롤되어 화면에 보여질 때position 5
에 해당하는ViewHolder
가cache
에 있었다면5라는 position
을 다시 바인딩할 필요없이 바로 재사용 할 수 있다
- 대신 "
요약
즉, ViewHolder를 cache에서 찾았다면 → view 변경없이 바로 재활용
pool에서 찾았다면 → 바인딩 필요
ViewHolder가 어디에도 존재하지 않으면 → 새로 생성되고 바인딩됨
ViewHolder 생성의 시점과 개수
(RecyclerView의 동작방식)
RecyclerView
가ViewHolder
를 재활용하는 과정에서 가장 상단에 있는View
가 사라지면 해당ViewHolder
가 스크롤시 하단에 나타나는View
에 바로 재활용된다고 생각하기 쉽다.- 그러나
ViewHolder
는 "바로" 재활용 되지 않는다. - 가장 처음
RecyclerView
가Adapter
에set 되었을 때
호출되는콜백함수
를 보면RecyclerView
가 화면에onAttachedToRecyclerView
가 호출되어 붙음- 화면에 총 4개의
ViewHolder
를 그리기 위한onCreateViewHolder
호출 LayoutManager
가addView
를 호출해itemView
를position
에 잘 붙인 후RecyclerView
에게 알리면RecyclerView
는Adapter
에게onViewAttachedToWindow
호출
- 중요한건, 화면에 최초로 보이는 4개의 ViewHolder를 생성하고 난 후 스크롤을 내렸을 때
- 바로 위에 있는
ViewHolder
인0번 holder
가 사라지고5번째 itemView
가 나타나면서0번째 ViewHolder
를 재사용할 것이기 때문에 바로onCreateViewHolder
를 건너뛰고onBindViewHolder
를 호출할 것이라 생각한다. - 하지만
5번째 itemView
가 나타나며 또ViewHolder
를 생성한다.8번째 itemView
가 나타날때까지ViewHolder
는 재활용되지 않고 계속 생성된다.
→ 이는RecyclerView
가 사라진ViewHolder
를 '바로' 재활용하지 않기 때문에 나타나는 현상이다 - 계속 스크롤하다가
9번째 itemView
가 만들어지는 순간 가장 먼저detached
되었던0번째 ViewHolde
r가onViewRecycled
되어 나타난다.onViewRecycled
는"재활용할 홀더를 가지고 왔음"
을 알리는 메서드이다. - 이후
9번째 itemView
부터는onCreateViewHolder
가 호출되지 않고detached
되었던ViewHolder
들이 차례로onViewRecycled
되어 재사용된다.
- 정리하자면,
9번째 itemView
가 나타났을 때가장 먼저 사라졌던 0번째 홀더
가 재사용됨을 알리는onViewRecycled:0
이 호출되고 이holder
가9번째 홀더
가 되어 bind되는 것이다.
→ 이는 바로ViewHolder
를 재사용한다는 의미의 콜백인onViewRecycled
가 호출되지 않는다는 것을 통해 알 수 있다 - 이후
RecyclerView
를 파괴하게 되면 화면에서 사라짐과 동시에onDetachedFromRecyclerView
가 호출된다.
RecyclerView Adapter 메소드 호출 순서
RecyclerView Adapter
는 아래의 4개의 메소드를 통해서 데이터를 바인딩한다.onCreateViewHolder
onBindViewHolder
getItemCount
getItemViewType
- 호출되는 순서를 살펴보면,
- 예를 들어 단말 화면에 10개의 리스트가 보인다면, 맨 처음
getItemCount()
가 호출되면서 총 item의 갯수가 몇개인지 판단한다. getItemViewType()
이 호출되면서, 현재Item View
의Position
에 해당하는ViewType
이 무엇인지를 판단한다.onCreateViewHolder
에서ViewType
에 해당하는ViewHolder
를 생성하여 리턴한다.
(ViewHolder는 각 Item View의 정보를 가지고 있는 애이기 때문에, Adapter는 계속해서 ViewHolder를 이용하여 Item View를 관리할 것이다. )onBindViewHolder
에서는 생성된ViewHolder
와Position
을 전달받아서, 현재Position
에 맞는data
를ViewHolder
가 관리하는View
들에 binding한다.
- 예를 들어 단말 화면에 10개의 리스트가 보인다면, 맨 처음
→ getItemCount()
가 1번 호출되고,
그 후 getItemViewType()
→ onCreateViewHolder()
→ onBindViewHolder()
가 10번씩 호출될 것이다.
출처 및 참고자료
RecyclerView ins and outs - Google I/O 2016
RecyclerView Deep Dive with Google I/O 2016
Recycler View 제대로 이해하기 - RecyclerView lifecycle
[안드로이드 공식문서 파헤치기] RecyclerView의 모든 것! - 1편(구조, 탄생배경)
[안드로이드] Recyclerview 제대로 알고 쓰자 !
Pluu Dev - RecyclerView#ViewHolder에서 ViewTreeLifecycleOwner 사용법
'IT > Android' 카테고리의 다른 글
[Android/Refactoring] Memory Leak - 1. 안드로이드 앱에서의 메모리 누수 찾기 (1) | 2023.07.21 |
---|---|
[Android] RecyclerView Deep Dive - 2. RecyclerView에서의 활용과 최적화 (0) | 2023.07.10 |
[Android/FCM] (5) Android에서의 알림 수신 구현 (0) | 2023.03.11 |
[Android/FCM] (4) Android 설정 (0) | 2023.03.07 |
[Android/FCM] (3) Registration Token 관리 (0) | 2023.03.02 |