Android 페이징3(Paging3)에 대해 알아보자
# Paging이란?
데이터를 가져올 때 한 번에 모든 데이터를 가져오는 것이 아니라 일정한 덩어리로 나눠서 가져오는 것을 뜻합니다.
페이징을 사용하면 성능, 메모리, 비용 측면에서 굉장히 효율적입니다.
# Jetpack Paging Library
- Android Jetpack에서는 페이징을 위한 Paging3 라이브러리를 제공.
- Paging3 라이브러리는 로컬 저장소에서나 네트워크를 통해 데이터를 나누어 효율적으로 로딩할 수 있게 도와줍니다.
- Paging3은 구글에서 권장하는 Android 앱 아키텍처에 맞게 설계되었으며, 다른 Jetpack 컴포넌트와 잘 동작할 수 있도록 설계되었습니다.
# Paging3의 장점
- 페이징 된 데이터의 메모리 내 캐싱.
앱이 페이징 데이터로 작업하는 동안 시스템 리소스를 효율적으로 사용할 수 있음 (코루틴 때문?)
이렇게 하면 앱이 페이징 데이터로 작업하는 동안 시스템 리소스를 효율적으로 사용할 수 있습니다. - 요청 중복 제거 기능이 기본으로 제공.
앱에서 네트워크 대역폭과 시스템 리소스를 효율적으로 사용할 수 있습니다. - 로드된 데이터의 끝까지 스크롤할 때 구성 가능한 RecyclerView 어댑터가 자동으로 데이터를 요청합니다.
- Kotlin 코루틴 및 Flow뿐만 아니라 LiveData 및 RxJava를 최고 수준으로 지원
- 새로고침 및 재시도 기능을 포함하여 오류 처리를 기본으로 지원
# Paging3 아키텍처
Paging3 라이브러리는 총 3개의 layer로 구성됩니다.
1. Repository layer
Repository layer의 페이징 라이브러리 구성요소는 PagingSource입니다.
각 PagingSource객체는 데이터 소스와 이 소스에서 데이터를 검색하는 방법을 정의합니다.
PagingSource객체는 네트워크 소스 및 로컬 데이터베이스를 포함한 단일 소스에서 데이터를 로드할 수 있습니다.
사용할 수 있는 다른 페이징 라이브러리 구성요소는 RemoteMediator입니다.
RemoteMediator객체는 로컬 데이터 베이스 캐시가 있는 네트워크 데이터 소스와 같은 계층화된 데이터 소스의 페이징을 처리합니다.
2. ViewModel layer
Pager 구성요소는 PagingSource 객체 및 PagingConfig 구성 객체를 바탕으로 반응형 스트림에 노출되는 PagingData 인스턴스를 구성하기 위한 공개 API를 제공합니다.
ViewModel 레이어를 UI에 연결하는 구성요소는 PagingData입니다.
PagingData 객체는 페이징 된 데이터의 스냅샷을 보유하는 컨테이너 입니다.
PagingSource 객체를 쿼리하여 결과를 저장합니다.
3. UI layer
UI 레이어의 기본 페이징 라이브러리 구성요소는 페이지로 나눈 데이터를 처리하는 RecyclerView 어댑터인 PagingDataAdapter입니다.
PagingDataAdapter 대신 AsyncPagingDataDiffer 구성요소를 사용하여 고유한 Custom Adapter를 사용할 수 있습니다.
(AsyncPagingDataDiffer를 직접 상속받아서 구현하여 사용할 수 있다.)
# Paging3 주요 클래스
PagingSource
네트워크 또는 데이터베이스에서 페이징 데이터를 로드하는 추상 클래스
이를 구현하려면 페이지 key 타입을 정의해야 합니다.
데이터를 검색하는 방법을 정의하는 클래스.
PagingSource에서 로드된 데이터는 PagingData 인스턴스로 관리 됩니다.
PagingData는 데이터가 추가적으로 로드됨에 따라 커질 수 있습니다.
하지만 기존에 로드된 데이터는 갱신되지 않습니다.
기존 Paging2와 다른점
Paging2에서 여러가지 방법으로 DataSource 처리를 하던 부분이 PagingSource 하나로 통합 되었다.
PagingSource는 데이터 소스를 정의하기 위한 클래스로 Key타입과, 반환할 데이터 형을 Generic으로 받습니다.
- load 함수는 실제 데이터를 가져오는 로직구현.
- getRefreshKey 함수는 초기 key값이나 데이터 로드 중단 후 재 로드시 이전 position에서 중단된 key값을 가져오는 등 load에서 사용할 key 값을 가져오는 로직구현
RemoteMediator
Network에서 데이터 로드와, 로드된 데이터를 내부 DB로 저장하는 역할을 수행합니다.
즉, Paging3에서 지원하는 내부DB캐싱에 관련된 역할을 수행하는 클래스입니다.
RemoteMediator를 사용하게 되면 PagingSource는 캐시된 데이터만을 사용하여 UI처리용 데이터를 처리합니다.
- initialize 함수는 캐시된 데이터를 갱신시킬 것인지에 대한 로직구현
- load함수는 데이터를 가져오기 위한 로직구현
RemoteMediator 사용 시 특이점은 Key를 내부에서 제공해주거나 사용하지 않는 점
load 함수에서 제공되는 LoadType을 활용하여 데이터 추가로드 여부에 대한 로직을 구현한 뒤,
결과를 load함수의 반환형인 MediatorResult에 endOfPaginationReached 파라미터에 넘겨주어 데이터 로드를 끝마칠지 판단합니다.
Pager
PagingSource 객체 및 PagingConfig 객체를 바탕으로 PagingData를 생성한 뒤 반응형 스트림화 해주는 클래스.
각 PagingData는 페이징 된 데이터 스냅샷을 나타내며, Pager로부터 Flow, Observabvle, LiveData 형태를 반환합니다.
PagingConfig
Pager 객체를 생성할 때 필수적으로 필요한 요소로써, 페이징을 구성하는 방법을 정의합니다.
페이징 하는 데이터의 크기, placeholder 사용 유무 등 PagingSource를 구성하는 방법을 정의합니다.
- 로드 대기 시간, 초기 로드의 크기 요청 등 PagingSource에서 콘텐츠를 로드하는 방법에 관한 옵션 설정
- 정의해야 하는 유일한 필수 매개변수는 각 페이지에 로드해야 하는 항목 수를 가리키는 페이지 크기이다
- 기본적으로 Paging은 로드하는 모든 페이지를 메모리에 유지한다.
사용자가 스크롤할 때 메모리를 낭비하지 않으려면 PagingConfig에서 maxSize 매개변수를 설정해야 한다. - PagingConfig.pageSize는 여러 화면의 항목이 포함될 만큼 충분히 커야 한다.
페이지가 너무 작으면 페이지의 콘텐츠가 전체 화면을 가리지 않기 때문에 목록이 깜박일 수 있다. - 기본적으로 PagingConfig.maxSize는 무제한이므로 페이지가 삭제되지 않는다.
페이지를 삭제하려면 사용자가 스크롤 방향을 변경할 때 네트워크 요청이 너무 많이 발생하지 않도록 maxSize를 충분히 큰 수로 유지해야 한다. (? 왠지 네트워크 요청이 많이 발생안하도록 maxSize를 크게 잡으라는 얘기인듯)
최소값은 pageSize + prefetchDistance * 2이다
→ 예제에서 보겠지만 페이징 방향이 바뀌면 반대 페이지의 API를 호출한다.
PagingData
PagingData는 페이징 된 데이터를 담아두는 컨테이너입니다.
PagingSource 객체를 쿼리 하여 결과를 저장하며, 최종적으로 반환되는 데이터는 일반적으로 UI layer의 PagingDataAdapter가 전달받게 됩니다.
PagingDataAdapter
RecyclerView에 데이터를 표시하는 주된 UI 구성 요소.
PagingData를 입력받아 내부적으로 언제 데이터를 추가해야 할지 관찰하게 됩니다.
PagingDataAdapter는 백그라운드 스레드에서 DiffUtil을 사용하여 데이터를 정제한 뒤에 데이터를 로드하기 때문에 UI스레드에서 새로운 항목을 추가할 때 부드럽게 나타낼 수 있습니다.
정리
Paging3는 PagingSource나 RemoteMediator가 PagingConfig의 정보를 토대로 Pager를 통해 PagingData를 생성한 뒤,
해당 인스턴스를 PagingDataAdapter가 활용하여 UI를 그리는 아키텍쳐로 설계
# 예제
API 사용 : https://pokeapi.co/api/v2/pokemon?offset=0&limit=20
설명 : 0부터 20개를 리스트 가져옴.
처음 호출
pageSize = 몇개를 Load할것인지.
maxSize = 최대 몇개를 저장할 것인지 실제 (pageSize = 20 하고 maxSize = 30, initialLoadSize = 20하면 에러남)
: 두배 이상의 max를 가져야 하는듯
: initialLoadSize = 40 으로 했을 때에는 에러나진 않았다. 초기 가져온 값이 2배 되면 괜찮은듯
리스트 끝날 때
# 추가 로드 상태
LoadState 객체를 통해 상태를 나타내며, 세가지 상태가 존재한다.
- LoadState.NotLoading : 활성 로드 작업이 없고 오류가 없음
- LoadState.Loading : 활성 로드 작업이 있음
- LoadState.Error : 오류가 있음
새로 고침시
CombinedLoadStates(refresh=Loading(endOfPaginationReached=false),
api 호출 : https://pokeapi.co/api/v2/pokemon?offset=0&limit=20
데이터 불러온 후
CombinedLoadStates(refresh=NotLoading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false), source=LoadStates(refresh=NotLoading(endOfPaginationReached=false), prepend=NotLoading(endOfPaginationReached=true), append=NotLoading(endOfPaginationReached=false)), mediator=null)
페이징
CombinedLoadStates(append=Loading(endOfPaginationReached=false)
api 호출 : https://pokeapi.co/api/v2/pokemon?offset=20&limit=20
데이터 불러온 후
NotLoading
스크롤 빠르게하여 페이징
append=Loading(endOfPaginationReached=false)
api 호출 : https://pokeapi.co/api/v2/pokemon?offset=40&limit=20
데이터 불러온 후
append=Loading(endOfPaginationReached=false)
NotLoading
참조 :
https://leveloper.tistory.com/202
http://labs.brandi.co.kr/2021/07/07/parkks2.html
'안드로이드' 카테고리의 다른 글
뷰 클릭효과 Ripple주기 (0) | 2022.05.31 |
---|---|
TextView 2개 붙어있을 때 앞에 ...붙이기 (0) | 2022.05.31 |
RecyclerView setHasFixedSize (0) | 2022.03.29 |
안드로이드 APK 아무나 삭제 못하도록 하는 adb 명령어 (0) | 2022.03.28 |
Android Adb(Android Debug Bridge) Path 설정(MAC) (0) | 2022.03.28 |