SharedPreferences는 간단한 설정 및 앱 구성을 저장하도록 설계된 Android의 키-값 데이터 저장소입니다. 데이터는 기기의 XML 파일에 저장되며, 이를 생성한 앱 내에서만 액세스할 수 있습니다. 공식 문서에 따르면 Android Developers, 2025, SharedPreferences는 기본 유형(String, Int, Boolean, Float, Long, Set<String>)의 저장을 지원합니다. 이는 SQL 쿼리나 파일 시스템 직접 작업 없이 소량의 사용자 설정을 저장하기 위한 가장 간단하고 빠른 솔루션입니다.
핵심 사항
SharedPreferences는 기기 내부 저장소의 XML 파일에 키-값 쌍을 저장하기 위한 Android의 내장 메커니즘입니다. API Level 1부터 사용 가능하며 추가 라이브러리가 필요하지 않습니다. 주요 목적은 사용자 기본 설정, 인터페이스 상태, 첫 실행 플래그 및 구조화된 데이터베이스가 필요하지 않은 기타 간단한 데이터를 저장하는 것입니다.
각 SharedPreferences 파일은 특정 이름 및 액세스 모드와 연결됩니다. 기본적으로 Context.MODE_PRIVATE 모드가 사용되며, 파일 액세스를 현재 앱으로만 제한합니다. 이전에는 Android가 MODE_WORLD_READABLE 및 MODE_WORLD_WRITEABLE 모드를 지원했지만, 보안상의 이유로 API Level 17에서 더 이상 사용되지 않으며 Android 7.0(API 24)에서 완전히 제거되었습니다.
단순함에도 불구하고 SharedPreferences는 수백만 개의 Android 앱에서 사용됩니다. Google에 따르면 Google Play에 게시된 앱의 90% 이상이 설정 저장에 SharedPreferences를 사용합니다. 그러나 복잡한 시나리오(대용량 데이터, 유형 안전성, 비동기)의 경우 Google은 Android Jetpack 라이브러리의 Preferences DataStore와 같은 더 현대적인 솔루션을 권장합니다.
물리적으로 SharedPreferences는 앱 디렉토리에 XML 파일로 저장됩니다: /data/data/{package_name}/shared_prefs/{file_name}.xml. 파일에는 저장된 값 유형에 따라 루트 요소 <map>과 하위 요소 <string>, <int>, <boolean>, <float> 및 <long>이 포함됩니다. 파일 크기는 제한이 없지만, 대용량 데이터(100KB 이상)의 경우 읽기 및 쓰기 성능이 눈에 띄게 저하되기 시작합니다.
SharedPreferences 파일은 기본적으로 암호화되지 않습니다. 데이터는 기기 파일 시스템에 일반 텍스트로 저장됩니다. 민감한 데이터(토큰, 비밀번호)를 저장하려면 AndroidX Security 라이브러리의 EncryptedSharedPreferences를 사용하는 것이 좋습니다. 이는 AES256-GCM으로 키와 값을 자동으로 암호화합니다.
SharedPreferences는 주기적인 디스크 동기화와 함께 메모리 내 캐싱 원리로 작동합니다. 파일에 처음 액세스할 때(getSharedPreferences를 통해) Android는 XML 파일을 RAM에 로드하고 Map 객체로 파싱합니다. 이후 모든 읽기 작업은 디스크에서 다시 읽지 않고 메모리에서 수행됩니다. 이는 높은 데이터 액세스 속도를 보장합니다.
쓰기 작업은 Editor — 내부 변경 버퍼를 사용합니다. 개발자가 putString 또는 putBoolean을 호출하면 변경 사항이 메모리의 Editor 객체에 저장됩니다. 디스크에 실제 쓰기는 commit(동기) 또는 apply(비동기) 메서드가 호출될 때 발생합니다. 이러한 메서드가 호출될 때까지 데이터가 저장되지 않으며, 앱이 예기치 않게 충돌하면 변경 사항이 손실될 수 있습니다.
SharedPreferences 인스턴스를 얻기 위해 두 가지 메서드가 사용됩니다: getPreferences 및 getSharedPreferences. 전자는 Activity 내에서만 사용 가능하며 Activity 이름으로 파일을 만듭니다. 후자는 더 유연하며 파일 이름과 액세스 모드를 허용하고 모든 컨텍스트(Application, Activity, Service)에서 액세스할 수 있습니다. 앱의 모듈이나 기능에 해당하는 파일 이름으로 getSharedPreferences를 사용하는 것이 좋습니다.
// SharedPreferences 가져오기
val prefs = context.getSharedPreferences(
"user_settings", Context.MODE_PRIVATE
)
// 데이터 쓰기
with(prefs.edit()) {
putString("username", "안나")
putInt("age", 28)
putBoolean("isLoggedIn", true)
apply()
}
// 데이터 읽기
val username = prefs.getString("username", "")
val age = prefs.getInt("age", 0)
val isLoggedIn = prefs.getBoolean("isLoggedIn", false)
MODE_MULTI_PROCESS(더 이상 사용되지 않음)를 사용하면 SharedPreferences가 프로세스 간에 동기화됩니다. 그러나 이 동기화는 원자성을 보장하지 않으며, Google은 멀티프로세스 시나리오에서 SharedPreferences 사용을 피할 것을 권장합니다. 이러한 경우 ContentProvider, 프로세스 간 액세스가 있는 Room 또는 DataStore를 사용하는 것이 좋습니다.
SharedPreferences는 키로 데이터를 읽기 위한 메서드 집합과 쓰기를 위한 Editor 인터페이스를 제공합니다. 각 읽기 메서드는 키와 키를 찾을 수 없는 경우 반환되는 기본값의 두 매개변수를 받습니다. 기본값은 반환 유형도 결정합니다: getString은 String을, getInt는 Int를 반환하는 식입니다.
| 읽기 메서드 | 쓰기 메서드 | 데이터 유형 |
|---|---|---|
| getString | putString | String |
| getInt | putInt | Int |
| getBoolean | putBoolean | Boolean |
| getFloat | putFloat | Float |
| getLong | putLong | Long |
| getStringSet | putStringSet | Set<String> |
Editor는 버퍼에 변경 사항을 수집하는 SharedPreferences의 내부 객체입니다. 모든 변경을 마친 후 개발자는 commit()(동기 쓰기) 또는 apply()(비동기 쓰기)를 호출합니다. 차이가 중요합니다: commit은 디스크에 완전히 기록될 때까지 현재 스레드를 차단하고 boolean(성공/실패)을 반환하는 반면, apply는 백그라운드 스레드에서 쓰기를 수행하고 즉시 제어를 반환하지만 결과는 반환하지 않습니다.
쓰기 결과를 알 필요가 없는 모든 경우 commit 대신 apply를 사용하는 것이 좋습니다. apply는 더 빠르고 UI 스레드를 차단하지 않습니다. commit은 데이터가 성공적으로 저장되었는지 확인하는 것이 중요한 경우나 멀티프로세스 모드에서 작업할 때만 사용해야 합니다. 개별 키를 제거하려면 remove 메서드, 완전히 지우려면 clear를 사용합니다. 모든 삭제 작업도 Editor를 통해 수행됩니다.
// 여러 변경 - 하나의 apply
prefs.edit {
putString("theme", "dark")
putBoolean("notifications", false)
remove("old_key")
}
// 값 변경 리스너
prefs.registerOnSharedPreferenceChangeListener { prefs, key ->
Log.d("TAG", "키가 변경됨: $key")
}
Android 12(API 31)부터 SharedPreferences는 Lifecycle을 통한 자동 구독 해제가 포함된 registerOnSharedPreferenceChangeListener 지원으로 강화되었습니다. 이는 잊혀진 리스너와 관련된 메모리 누수를 방지하는 데 도움이 됩니다. 이전 버전에서는 개발자가 컴포넌트의 onDestroy 또는 onStop에서 수동으로 unregisterOnSharedPreferenceChangeListener를 호출해야 합니다.
광범위한 사용에도 불구하고 SharedPreferences는 Android의 모든 데이터 저장 시나리오에 대한 보편적인 솔루션이 아닙니다. 데이터 볼륨, 유형 안전성 요구 사항 및 성능에 따라 Google은 Android Jetpack 및 표준 Android 라이브러리에 포함된 다양한 대안을 권장합니다.
| 솔루션 | 사용 시기 | 단점 |
|---|---|---|
| SharedPreferences | 소규모 설정(최대 100개 키) | 유형 안전성 없음, 동기 읽기 |
| DataStore | 코루틴을 사용한 중간 복잡도 설정 | API 14 미만에서 하위 호환성 없음 |
| Room | 구조화된 데이터 및 목록 | 3-5개 설정에는 과도함 |
| EncryptedSharedPreferences | 민감한 데이터 및 토큰 | AndroidX Security에 의존 |
DataStore는 Google이 SharedPreferences의 대체품으로 소개한 Android Jetpack 라이브러리입니다. 두 가지 변형을 제공합니다: Preferences DataStore(SharedPreferences와 같은 키-값) 및 Proto DataStore(Protocol Buffers를 통한 유형화된 저장소). DataStore는 비동기 작업에 코루틴과 Flow를 사용하며, 유형 안전성을 보장하고 버전 마이그레이션을 자동으로 처리합니다. Google은 모든 새 프로젝트에 DataStore를 권장합니다.
DataStore의 주요 장점은 API 수준의 비동기성입니다. 모든 읽기 작업은 Flow를 반환하고 쓰기 작업은 suspend 함수입니다. 이는 SharedPreferences 동기 읽기에서 발생할 수 있는 UI 스레드 차단을 완전히 제거합니다. 또한 DataStore는 데이터 일관성을 보장합니다: 쓰기는 트랜잭션에서 수행되며, 실패 시 모든 변경 사항이 롤백됩니다.
실용적인 예를 살펴보겠습니다: Android 앱의 테마 설정(라이트/다크/시스템). 사용자가 테마를 선택하면 선택 항목이 SharedPreferences에 저장됩니다. 이후 앱 실행 시 저장된 설정에서 테마가 복원됩니다. 반응형 UI 업데이트를 위해 SharedPreferences.OnSharedPreferenceChangeListener를 통한 변경 관찰이 사용됩니다.
테마에 대한 SharedPreferences 작업을 모두 캡슐화하는 ThemePreferences 클래스를 만들어 보겠습니다. 클래스는 getTheme(읽기), setTheme(쓰기) 및 observeTheme(관찰) 메서드를 제공합니다. 설정 파일 이름은 MODE_PRIVATE 모드로 “app_preferences”입니다. 편의를 위해 키는 상수로 컴패니언 객체에 배치됩니다.
class ThemePreferences(context: Context) {
companion object {
private const val PREF_NAME = "app_preferences"
private const val KEY_THEME = "theme_mode"
const val THEME_LIGHT = "light"
const val THEME_DARK = "dark"
const val THEME_SYSTEM = "system"
}
private val prefs = context
.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
fun getTheme(): String =
prefs.getString(KEY_THEME, THEME_SYSTEM) ?: THEME_SYSTEM
fun setTheme(theme: String) {
prefs.edit { putString(KEY_THEME, theme) }
}
fun observeTheme(callback: (String) -> Unit) {
prefs.registerOnSharedPreferenceChangeListener { _, key ->
if (key == KEY_THEME) {
callback.invoke(getTheme())
}
}
}
}
Activity 또는 Fragment에서 ThemePreferences 인스턴스 얻기는 앱 컨텍스트를 통해 수행됩니다. 초기화 시 현재 테마를 설정하기 위해 getTheme이 호출됩니다. 사용자가 새 테마를 선택하면 setTheme이 호출되고 observeTheme을 통해 Activity를 다시 시작하지 않고 인터페이스가 업데이트됩니다. 메모리 누수를 방지하기 위해 onDestroy에서 리스너를 구독 해제하는 것이 중요합니다. 특히 구성 변경 시 Activity가 다시 생성되는 경우 더욱 그렇습니다.
최소 대상 버전이 Android 12+인 앱의 경우 LifecycleObserver와 함께 registerOnSharedPreferenceChangeListener를 사용하는 것이 좋습니다. 이는 구성 요소 수명 주기 변경 시 구독 및 구독 해제를 자동으로 관리합니다. 이전 버전의 경우 구독 및 구독 해제를 수동으로 관리해야 하며, 이는 SharedPreferences를 사용하는 프로덕션 앱에서 빈번한 오류 원인입니다.
자주 묻는 질문
SharedPreferences는 기본 유형과 Set<String>만 직접 지원합니다. 객체를 저장하려면 Gson 또는 Moshi를 통해 JSON 문자열로 직렬화하고 putString으로 저장한 다음 읽을 때 역직렬화해야 합니다. 필드가 많은 복잡한 객체의 경우 JSON 직렬화와 함께 SharedPreferences 대신 Room을 사용하는 것이 좋습니다.
네, SharedPreferences는 스레드 안전합니다. 모든 읽기 및 쓰기 작업은 SharedPreferences 객체 및 해당 Editor 수준에서 동기화됩니다. 그러나 멀티프로세스 모드를 사용할 때는 동기화가 보장되지 않습니다. 단일 앱 내에서 여러 스레드의 동시 액세스의 경우 SharedPreferences는 추가 잠금 없이 안전합니다.
SharedPreferences에서 모든 데이터를 완전히 지우려면 Editor에서 clear() 메서드를 호출하고 apply를 통해 변경 사항을 적용하세요. XML 파일 자체를 삭제해야 하는 경우 컨텍스트에서 deleteSharedPreferences(name)을 사용하세요. 설정 → 앱 → 데이터 지우기를 통해 앱 데이터를 지우면 모든 SharedPreferences 파일도 제거됩니다.
새 프로젝트의 경우 Google은 SharedPreferences의 대안으로 DataStore를 권장합니다. DataStore는 코루틴을 사용한 비동기 작업, 유형 안전성(Proto DataStore) 및 자동 마이그레이션을 제공합니다. SharedPreferences는 최소 버전이 API 14 미만인 프로젝트 또는 추가 종속성 없이 빠른 통합이 필요한 경우에만 선택해야 합니다.
데이터 암호화를 위해 AndroidX Security 라이브러리의 EncryptedSharedPreferences를 사용하세요. AES-256 GCM으로 키와 값을 자동으로 암호화합니다. 설정 과정은 최소화됩니다: getSharedPreferences를 Android Keystore의 마스터 키를 지정하는 EncryptedSharedPreferences.create로 대체합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.