[Android] 안전하게 로컬에 값 저장하기 (SharedPreferences vs DataStore)

강승구·2023년 3월 4일

SharedPreferences

안드로이드 앱 개발을 진행하다 보면 앱의 데이터들을 저장하여 관리해야 할 상황이 존재한다. 데이터의 양이 많거나 중요 데이터의 경우 서버나 DB에 저장해야겠지만, 간단한 설정 값이나 문자열 같은 데이터를 저장하기 위해 SQLite나 Room을 사용하기는 부담스럽기 때문에 SharedPreferences를 사용하는 것이 적합하다.

SharePreferences는 다음과 같은 특징을 갖는다.

  • 보통 초기 설정값이나 자동 로그인 여부 등 간단한 값을 저장하기 위해 사용
  • Application에 파일 형태로 데이터를 저장한다.
  • Application이 삭제되기 전까지 저장한 데이터가 보존된다.
  • Key-value 방식

MODE의 종류

  • MODE_PRIVATE : 생성한 Application에서만 사용 가능하다.
  • MODE_WORLD_READABLE : 외부 App에서 사용 가능, But 읽기만 가능
  • MODE_WORLD_WRITEABLE : 외부 App에서 사용 가능, 읽기/쓰기 가능

사용 방법

1. 애플리케이션 Context로부터 SharedPreference에 값 저장하기

binding.saveBtn.setOnClickListener {
            val sharedPreference = getSharedPreferences("sp1", MODE_PRIVATE)
            val editor  : SharedPreferences.Editor = sharedPreference.edit()
            editor.putString("hello","안녕하세요")
            editor.putString("good bye","안녕히 가세요")

            editor.commit() // data 저장!
        }

2. 데이터 불러오기

binding.loadBtn .setOnClickListener {
            val sharedPreference = getSharedPreferences("sp1", MODE_PRIVATE)
            val value1 = sharedPreference.getString("hello", "데이터 없다1")
            val value2 = sharedPreference.getString("good bye", "데이터 없다2")
            Log.d("key-value", "Value 1 : " + value1)
            Log.d("key-value", "Value 2 : " + value2)

        }

3. 데이터 삭제하기

binding.deleteBtn.setOnClickListener {
            val sharedPreference = getSharedPreferences("sp1", MODE_PRIVATE)
            val editor = sharedPreference.edit()

            editor.remove("hello")
            // 전체 삭제는 editor.clear()
            editor.commit()
        }

Why DataStore?

SharedPreference를 이용하면 로컬에 저장해야할 값들을 간편하게 관리할 수 있다.
하지만 안드로이드 공식문서에서는 SharedPreference 대신, DataStore를 사용할 것을 권장하고 있다.

우선, SharedPreferences는 암호화되지 않은 상태로 데이터를 저장하기 때문에 JWT Token과 같은 민감한 정보를 저장하는 용도로는 적합하지 않다.
이러한 문제를 해결하기 위해 EncryptedSharedPreferences를 사용해 데이터를 암호화해서 저장할 수 있긴 하지만 근본적으로 SharedPreference를 기반으로 데이터를 관리하는 것에는 다음과 같은 문제들이 있다.

  1. RunTimeException에 취약하다.
    SharedPreferences 는 기본적으로 Exception에 대한 에러 핸들링을 제공하고 있지 않는다. 따라서 SharedPreferences 때문에 발생하는 Exception을 다루기에는 어려움이 있다.

  2. UI Thread 에 안전하지 않다.
    ~하는commit() 함수는 다음과 같이 구현되어있다.

해당 함수에서는 별도의 쓰레드가 아닌 호출된 Thread에서 바로 File Write 를 하고 있다.
writtenToDiskLatch.await() 부분을 보면 알 수 있듯이, 쓰기 작업이 완료될 때까지 현재 스레드를 대기 상태로 만들기 때문에 이 코드가 UI 스레드에서 실행되면, UI 스레드가 차단되어 작업이 끝날 때까지 사용자 입력(터치, 클릭 등)을 처리하지 못하게 된다.

이는 파일에 쓰는 데이터가 많지 않으면 문제가 없어 보일 수 있지만 저사양 기기에서나, 데이터 양이 많아지면 UI Thread 를 오랫동안 Block 하게 되고 유저에게 버벅이는 경험을 주거나 ANR을 발생시킬 수 있다.

이러한 이유로 안드로이드 측에서는 DataStore 사용을 권장하고 있다.
도입에서 설명했듯이 DataStore는 SharedPreferences를 대체하기 위한 라이브러리이다.

Preferences Datastore

Preference Datastore는 DataStore 인터페이스를 구현하는 구현체로, 간단한 데이터를 key-value 쌍으로 저장하기 위해 사용하는 라이브러리이다. SharedPreference와 같이 Type Safety를 제공하지 않는 데이터 저장소 솔루션이다. 하지만 Type Safety을 제공하지 않아 데이터 저장소에서 꺼낸 데이터에 대해 타입을 잘못 지정한다면 오류가 발생할 수 있다.

그럼에도 비동기 작업을 위해 Coroutines를 기본으로 사용한다는 점과 데이터 업데이트를 Transaction으로 처리해 Strong Consistency를 보장해 다중 스레드 환경에 최적화 되어 있다는 점이 장점이다.

Proto DataStore

profile
강승구

0개의 댓글