목차.
3. Room을 활용해 offline cache 구현하기
안녕하세요 :)
오늘은 안드로이드 Room을 이용한 오프라인 캐시에 대해 얘기해 보겠습니다.
로딩 애니메이션을 좋아하는 사람은 아무도 없습니다. 우리는 좀 더 나은 사용자 경험을 제공하기 위해 오프라인 캐시를 사용해야 합니다. 데이터를 로컬로 유지하는 것은 여러 가지 면에서 유리하게 작용됩니다. 많은 데이터를 처리할 때 캐싱을 통해 응답 속도를 개선할 수 있고, 또 사용자가 네트워크에 액세스 할 수 없는 상황에서 오프라인 상태로 계속 콘텐츠를 탐색할 수 있게 해줍니다.
offline cache를 개발하게 된 계기
출퇴근 관련 앱 프로젝트 개발 도중, 화면에 근무기록 데이터를 서버에서 불러와 적용할 때까지 시간이 조금 지연됐습니다. 특히 근무기록 필터를 사용해 최대 1년간의 근무기록을 불러올 때는 더미 데이터로 테스트한 결과 약 3초의 지연시간이 발생했습니다. 지연 시간을 단축시키기 위해 room을 이용한 cache를 개발하게 되었습니다.
왜 캐싱에 Room을 사용해야 할까?
Room 지속성 라이브러리는 SQLite를 완벽히 활용하면서 원활한 데이터베이스 액세스가 가능하도록 SQLite의 추상화 계층을 제공합니다. Room을 사용하면 다음과 같은 이점이 있습니다.
- 사용 및 구현이 쉽습니다.
- LiveData를 사용한 실시간 모니터링에 적합합니다.
- Room에서 다양한 구성 요소를 쉽게 테스트할 수 있습니다.
- 컴파일 타임 검사를 제공합니다.
- 상용구 코드의 양을 줄입니다.
자세한 사항은 안드로이드 공식 문서를 참고해 주세요.
Room을 활용해 offline cache 구현하기
프로젝트는 MVVM 패턴을 따르며 비동기 처리를 위한 Coroutine, 의존성 주입을 위한 Hilt를 사용했습니다.
UI에 표시되는 모든 데이터는 ViewModel에서 Repository를 통해 가져옵니다. 로컬 또는 원격 데이터를 불러오고 저장하는 등 모든 과정은 이 Repository 내에서 발생합니다.
Repository 안에서 Flow를 이용해 Local 데이터와 Remote 데이터 작업을 어떻게 할 것인지 구현합니다. Flow는 비동기 데이터 스트림을 지원하며, 작업을 순차적으로 처리해 값을 반환합니다. 먼저 Room에 있는 데이터를 불러와 반환하고, 서버에서 데이터를 불러옵니다. 서버에서 데이터를 받으면 기존 Room 데이터와 비교합니다. 기존 Room과 다르면 Room을 새로 업데이트합니다.
ViewModel에서는 Repository에서 받은 Flow를 asLiveData()를 통해 LiveData 객체로 변환합니다. LiveData가 변경되면 뷰에서 관찰자가 호출되며 UI가 업데이트됩니다.
화면에 근무기록을 불러오는 과정
- 기존 Room 데이터가 있으면 바로 근무기록 데이터를 Room에서 가져와 보여줍니다.
- 서버 api로 요청 후 응답받은 근무기록 데이터를 기존 Room에 있는 데이터와 비교합니다.
- 데이터 비교 후 값이 다르면 화면에 로딩을 표시하고 Room을 새로 업데이트합니다.
- Room이 업데이트되면 LiveData의 관찰자가 호출되고 UI가 업데이트됩니다. 화면에 로딩을 숨깁니다.
기타)
* 데이터를 업데이트할 때 순서 주의하기
서버를 업데이트한 뒤 Room을 업데이트해야 합니다.
서버를 먼저 업데이트하는 이유는 다음 질문을 생각해 보면 됩니다. 서버 업데이트 요청과 Room 업데이트 요청 중 어느 것이 더 실패할 가능성이 높을까요? 네트워크 불안정 및 연결 실패, 높은 트래픽, api 서버 오류 등 서버 업데이트 요청이 실패할 원인은 다양합니다. Room을 먼저 업데이트한다면, 로컬과 원격 데이터베이스 간에 불일치가 발생할 가능성이 있습니다.
사용한 Room 구조와 동작 방식
Room은 Entity, Database, Dao 3가지를 사용합니다.
@Entity를 사용해 데이터베이스 테이블을 정의합니다.
@Dao를 사용하여 데이터베이스에 액세스할 내용을 정의합니다.
@Database로 데이터베이스를 보유할 클래스를 정의합니다. RoomDatabase를 상속받는 추상 클래스입니다.
<동작 원리>
Room은 데이터베이스를 생성한 후 코드를 처음으로 컴파일할 때 @Database 및 @Dao 주석 클래스의 구현을 자동 생성합니다. Room annotationProcessor(주석 프로세서)에 의해 자동으로 생성됩니다. 프로젝트의 WorkRecordDatabase 구현 클래스는 WorkRecordDatabase Impl이고 WorkRecordDao 구현 클래스는 WorkRecordDao Impl입니다. 구현 클래스에서 실제 처리가 이루어집니다.
* WorkRecordDatabase Impl
createOpenHelper()
Room을 사용하여 데이터베이스 인스턴스를 만들 때 호출됩니다. 데이터베이스 생성 및 버전 관리를 관리하기 위한 SQLite OpenHelper 인터페이스 SupportSQLiteOpenHelper를 생성하고 반환합니다.
createInvalidationTracker()
테이블 목록을 관찰 및 추적하고 업데이트 사항을 콜백 합니다.
clearAllTables()
지정된 데이터베이스의 모든 테이블에서 데이터를 삭제합니다.
WorkRecordDao()
Dao 클래스가 아직 존재하지 않을 경우 Dao Impl 인스턴스를 생성하고 반환합니다.
* WorkRecordDao Impl
__db
RoomDatabase 인스턴스입니다.
__insertionAdapterOfWorkRecord
entity를 테이블에 삽입하는 데 사용되는 EntityInsertionAdapter 인스턴스입니다.
__deletionAdapterOfWorkRecord
entity를 업데이트/삭제하는 데 사용되는 EntityDeletionOrUpdateAdapter 인스턴스입니다.
Dao에서 INSERT, DELETE, UPDATE는 __insertionAdapterOfWorkRecord와 __deletionAdapterOfWorkRecord를 이용합니다. Dao에서 SELECT(조회)를 할 때는 @Query 주석에 지정된 쿼리를 기반으로 RoomSQLiteQuery 객체를 생성합니다. 그런 다음 cursor를 만들어 데이터베이스에서 데이터를 검색합니다.
아래는 프로젝트와 별개로 Room를 사용해 개발한 오프라인 캐싱 샘플 코드입니다.
https://github.com/shruddms/OfflineCachingSample
offline cache 개발 후기
근무기록을 불러올 때 매번 로딩 프로그래스바가 뜨는 것이 거슬렸는데 Room을 활용해 offline cache를 구현함으로써 로컬에 캐시 된 데이터를 불러와 속도도 빨라지고 로딩도 안 보일 수 있게 되었습니다. 1년간의 근무기록을 불러올 때는 3초에서 1초로 지연시간이 개선되었습니다. 캐싱 지원은 더 나은 사용자 경험을 위한 효과적인 작업이라고 느꼈습니다.
'Main' 카테고리의 다른 글
[Android] 다양한 폼 팩터를 위한 UI 개발 (4) | 2022.12.30 |
---|---|
[Android] Github Actions으로 CI/CD 구축 (8) | 2022.12.21 |
[Android] Service를 활용한 Timer 개발 (0) | 2022.11.30 |
MVP 앱 개발 (4) | 2022.11.06 |
[Android] 앱 아키텍처 설계 (0) | 2022.11.03 |
댓글