SharedPreferences와 DataStore, 그리고 프로토콜버퍼

이윤설·2024년 10월 12일
1

안드로이드 연구소

목록 보기
22/33

SharedPreferences

정의

SharedPreferences는 키-값 쌍의 형태로 간단한 데이터를 저장할 수 있는 API다.
주로 앱의 설정이나 사용자 정보를 저장하는 데 사용된다.

특징

  • 비관계형 데이터: 데이터를 구조화할 필요 없이 간단하게 저장할 수 있다.
  • 영속성: 앱이 종료되어도 데이터는 유지된다.
  • 쓰레드 안전: UI 스레드와 별개로 작동하여 데이터가 안전하게 저장된다.

SharedPreferences는 오랫동안 Android에서 간단한 데이터 저장소로 사용되었지만, 몇 가지 한계점이 있다. 이러한 문제점들을 보완하기 위해 DataStore가 등장하게 되었고, Preferences DataStore는 SharedPreferences의 대안으로 추천된다.

한계점

  1. UI 스레드에서 동작: SharedPreferences는 기본적으로 UI 스레드에서 동작한다. 이는 데이터 저장이나 로딩 과정에서 애플리케이션의 성능을 저하시킬 수 있고, 잠재적으로 UI 지연을 초래할 수 있다.

  2. 비동기 처리 부족: SharedPreferences는 데이터를 비동기적으로 처리하지 않는다.
    이로 인해 긴 저장/읽기 작업이 있을 경우 앱이 일시적으로 멈추거나 느려질 수 있다.

  3. 데이터 일관성 문제: SharedPreferences는 트랜잭션을 보장하지 않기 때문에 여러 스레드에서 동시 접근할 경우, 데이터의 일관성이 깨질 수 있다.

  4. 안정성 부족: SharedPreferences는 파일 시스템 기반이므로, 잘못된 쓰기 작업이나 중단된 프로세스로 인해 데이터가 손상될 위험이 있다.

DataStore

Jetpack DataStore는 프로토콜 버퍼를 사용하여 키-값 쌍 또는 유형이 지정된 객체를 저장할 수 있는 데이터 저장소 솔루션이다. DataStore는 Kotlin 코루틴과 Flow를 활용하여 비동기적이고 일관된 트랜잭션 방식으로 데이터를 저장한다.

현재 SharedPreferences를 사용하여 데이터를 저장하고 있다면, DataStore로 이전하는 것이 좋다. DataStore는 소규모 단순 데이터 세트에 적합하며, 부분 업데이트나 참조 무결성을 지원하지 않는다. 복잡한 대규모 데이터 세트와 부분 업데이트, 참조 무결성을 지원해야 할 경우에는 DataStore 대신 Room을 사용하는 것이 권장된다.

Preferences DataStore 및 Proto DataStore

DataStore는 두 가지 구현체인 Preferences DataStore와 Proto DataStore를 제공한다.

  • Preferences DataStore:

    • 정의: 키-값 쌍을 사용하여 간단하게 데이터를 저장하고 조회하는 방식이다. 데이터에 대한 사전 정의된 스키마가 필요하지 않으며, 타입 안전성을 제공하지 않는다.
    • 사용 예: 사용자 설정(예: 알림 설정, 테마 선택 등)과 같이 간단한 데이터를 저장할 때 주로 사용된다.
  • Proto DataStore:

    • 정의: 프로토콜 버퍼를 사용하여 데이터의 스키마를 정의하고, 타입 안전하게 데이터를 저장하는 방식이다. 복잡한 데이터 구조를 효과적으로 저장할 수 있다.
    • 사용 예: 구조화된 데이터(예: 사용자 프로필, 복잡한 설정 등)를 저장할 때 유용하다.

둘 중 더 자주 사용되는 것은 Preferences DataStore이다.
왜냐하면 구현이 비교적 간단하기 때문이다.
반면, Proto DataStore는 더 복잡한 데이터 구조가 필요한 경우에 사용된다.

DataStore를 올바르게 사용하기 위한 규칙

  1. DataStore 인스턴스 중복 생성 금지: 같은 프로세스에서 특정 파일의 DataStore 인스턴스를 두 개 이상 만들지 않아야 한다. 이렇게 하면 DataStore의 모든 기능이 중단될 수 있다. 동일한 프로세스에서 특정 파일의 DataStore가 여러 개 활성화될 경우, 데이터를 읽거나 업데이트할 때 IllegalStateException이 발생한다.

  2. 변경 불가능한 타입 사용: DataStore에 사용된 유형은 변경 불가능해야 한다.
    유형을 변경하면 DataStore가 제공하는 모든 보장이 무효화되고, 심각하고 포착하기 어려운 버그가 발생할 수 있다.

  3. SingleProcessDataStore와 MultiProcessDataStore 혼용 금지: 동일한 파일에서 SingleProcessDataStore와 MultiProcessDataStore를 함께 사용하지 않아야 한다. 둘 이상의 프로세스에서 DataStore에 액세스하려는 경우 항상 MultiProcessDataStore를 사용해야 한다.

  • SingleProcessDataStore:

    • 정의: 동일한 프로세스 내에서 단일 DataStore 인스턴스를 사용하는 방식이다. 데이터 저장소의 모든 인스턴스가 동일한 파일을 공유하여 데이터를 읽고 쓸 수 있다.
    • 특징:
      • 프로세스 내에서만 작동하므로 데이터 일관성을 유지하기 쉽다.
      • 여러 인스턴스가 동일한 파일에 접근할 경우 IllegalStateException을 발생시킬 수 있다.
  • MultiProcessDataStore:

    • 정의: 여러 프로세스에서 동일한 DataStore 인스턴스에 접근할 수 있도록 하는 방식이다. 여러 앱이나 프로세스가 데이터에 접근할 수 있는 환경을 제공한다.
    • 특징:
      • 여러 프로세스 간 데이터 일관성을 보장한다.
      • 데이터 저장소에 액세스하는 경우 MultiProcessDataStore를 사용해야 한다.

DataStore를 사용해야 하는 이유

  1. 비동기 처리 및 코루틴 사용: DataStore는 Kotlin 코루틴과 Flow를 사용해 비동기적으로 데이터를 처리한다. 이를 통해 UI 스레드가 차단되지 않으며, 더욱 빠르고 부드러운 사용자 경험을 제공한다.

  2. 트랜잭션 지원: DataStore는 트랜잭션 방식으로 데이터를 저장한다. 여러 작업이 동시에 발생하더라도 데이터의 일관성이 보장된다.

  3. 유형 안전성: 특히 Proto DataStore는 사용자 정의 데이터 유형을 통해 데이터의 타입 안전성을 보장한다. 이는 코드의 신뢰성을 높여주고, 오류 가능성을 줄여준다.

  4. 확장성: DataStore는 SharedPreferences보다 더욱 확장 가능하며, 대규모 데이터 세트를 관리하는 데 적합한 구조를 제공한다.

결론적으로, DataStore는 성능과 안정성 면에서 SharedPreferences를 능가하며, 특히 현대적 Android 앱 개발에서 필수적인 비동기 처리 및 데이터 일관성을 지원한다.

알림 설정 예시

SharedPreferences

// 저장
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.putBoolean("notification_enabled", true)
editor.apply()

// 읽기
val notificationEnabled = sharedPreferences.getBoolean("notification_enabled", false)

DataStore

val NOTIFICATION_ENABLED = booleanPreferencesKey("notification_enabled")

// 저장
suspend fun setNotificationEnabled(enabled: Boolean) {
    context.dataStore.edit { preferences ->
        preferences[NOTIFICATION_ENABLED] = enabled
    }
}
setNotificationEnabled(true)   // 알림 켜기

// 읽기
val notificationEnabledFlow: Flow<Boolean> = context.dataStore.data
    .map { preferences ->
        preferences[NOTIFICATION_ENABLED] ?: false
    }

만약 사용자가 특정 앱의 알림설정을 켰다면, 이 설정은 DataStore에 다음과 같이 저장된다.
NOTIFICATION_ENABLED: true <- 이때, true는 매개변수 enabled이다.

참고로 ?: false는 값을 false로 변경하는 것이 아니다. 이는 "엘비스 연산자"라고 불리는 Kotlin의 null 안전 연산자다. 이 연산자의 의미는 다음과 같다.

preferences[NOTIFICATION_ENABLED]가 null이 아니면 그 값을 사용한다. null이면 false를 사용한다.

즉, 이는 알림 설정이 저장되어 있지 않은 경우(null인 경우) 기본값으로 false를 사용하겠다는 의미이다.
알림 설정을 끄는 것이 아니라, 설정 값이 없을 때의 기본 동작을 정의하는 것이다.


cf. 프로토콜 버퍼

프로토콜 버퍼는 구조화된 데이터를 직렬화하는 방법이다.
쉽게 말해, 데이터를 저장하거나 네트워크로 전송할 때 사용하는 효율적인 포맷이다.

  1. 기본 개념:

    • 데이터의 구조를 정의하는 언어를 제공한다.
    • 이 정의를 바탕으로 다양한 프로그래밍 언어에서 사용할 수 있는 코드를 자동으로 생성한다.
  2. 장점:

    • 작고 빠름: JSON이나 XML보다 데이터 크기가 작고 처리 속도가 빠르다.
    • 언어 중립적: 여러 프로그래밍 언어에서 사용할 수 있다.
    • backwards compatibility: 새로운 필드를 추가해도 기존 코드와 호환된다.
  3. 사용 예:
    간단한 예로, 사용자 정보를 정의한다면 다음과 같이 할 수 있다.

   message User {
     string name = 1;
     int32 age = 2;
     string email = 3;
   }

이 정의를 바탕으로 각 언어별 코드가 생성되고, 이를 이용해 데이터를 쉽게 직렬화/역직렬화할 수 있다.

  1. 사용 사례:
    • 네트워크 통신: 서버-클라이언트 간 데이터 교환
    • 데이터 저장: 구조화된 데이터를 파일로 저장
    • 마이크로서비스: 서비스 간 통신

프로토콜 버퍼 Vs. Json

JSON은 가독성이 좋은 대신, 텍스트 형식이라 프로토콜버퍼보다 크기가 크다는 단점이 있다.
프로토콜버퍼는 가독성이 비교적 안좋은 대신, 바이너리 형식이라 JSON보다 크기가 작다는 장점이 있다.

따라서 데이터의 크기가 중요한 상황에선 프로토콜 버퍼를 사용하는 것이 좋고, 그렇게 중요하지 않다면 JSON을 사용하는 것이 좋을 것이다. 이것도 무조건적인 것은 아니고 어차피 둘 다 요새 자주 쓰이기에 참고만 하자.

(출처: https://bigdown.tistory.com/841)

profile
화려한 외면이 아닌 단단한 내면

0개의 댓글

관련 채용 정보