SharedPreferences to magazyn danych typu klucz-wartość w Androidzie, przeznaczony do przechowywania prostych ustawień i konfiguracji aplikacji. Dane są przechowywane w pliku XML na urządzeniu i dostępne tylko w obrębie aplikacji, która je utworzyła. Zgodnie z oficjalną dokumentacją Android Developers, 2025, SharedPreferences obsługuje przechowywanie typów pierwotnych: String, Int, Boolean, Float, Long i Set<String>. To najprostsze i najszybsze rozwiązanie do przechowywania niewielkich ilości ustawień użytkownika bez konieczności korzystania z zapytań SQL lub bezpośredniej pracy z systemem plików.
Najważniejsze
SharedPreferences to wbudowany mechanizm Androida do przechowywania par klucz-wartość w pliku XML w wewnętrznej pamięci urządzenia. Jest dostępny od API Level 1 i nie wymaga podłączania dodatkowych bibliotek. Główne przeznaczenie — zapisywanie ustawień użytkownika, stanu interfejsu, flag first-launch i innych prostych danych, które nie wymagają strukturalnej bazy danych.
Każdy plik SharedPreferences jest powiązany z konkretną nazwą i trybem dostępu. Domyślnie używany jest tryb Context.MODE_PRIVATE, który ogranicza dostęp do pliku tylko bieżącej aplikacji. Wcześniej Android obsługiwał tryby MODE_WORLD_READABLE i MODE_WORLD_WRITEABLE, ale zostały one uznane za przestarzałe od API Level 17 i całkowicie usunięte w Androidzie 7.0 (API 24) ze względów bezpieczeństwa.
Mimo swojej prostoty, SharedPreferences jest używany w milionach aplikacji na Androida. Według Google, ponad 90% aplikacji opublikowanych w Google Play używa SharedPreferences do przechowywania ustawień. Jednak dla złożonych scenariuszy (duże ilości danych, bezpieczeństwo typów, asynchroniczność) Google zaleca bardziej nowoczesne rozwiązania, takie jak Preferences DataStore z biblioteki Android Jetpack.
Fizycznie SharedPreferences jest przechowywany jako plik XML w katalogu aplikacji: /data/data/{package_name}/shared_prefs/{file_name}.xml. Plik zawiera element główny <map> z elementami potomnymi <string>, <int>, <boolean>, <float> i <long> w zależności od typu zapisanej wartości. Rozmiar pliku nie jest ograniczony, ale dla dużych ilości danych (powyżej 100 KB) wydajność odczytu i zapisu zaczyna zauważalnie spadać.
Pliki SharedPreferences nie są domyślnie szyfrowane. Dane są przechowywane w otwartej postaci w systemie plików urządzenia. Do przechowywania wrażliwych danych (tokeny, hasła) zaleca się używanie EncryptedSharedPreferences z biblioteki AndroidX Security, która automatycznie szyfruje klucze i wartości za pomocą AES256-GCM.
SharedPreferences działa na zasadzie buforowania w pamięci z okresową synchronizacją na dysk. Przy pierwszym dostępie do pliku (przez getSharedPreferences) Android ładuje plik XML do pamięci RAM i parsuje go do obiektu Map. Wszystkie kolejne operacje odczytu są wykonywane z pamięci, bez ponownego odczytu z dysku. Zapewnia to wysoką szybkość dostępu do danych.
Operacje zapisu używają Editor — wewnętrznego bufora zmian. Gdy programista wywołuje putString lub putBoolean, zmiany są przechowywane w obiekcie Editor w pamięci. Faktyczny zapis na dysk następuje przy wywołaniu metody commit (synchronicznie) lub apply (asynchronicznie). Do czasu wywołania tych metod dane nie są zapisywane, a w przypadku awaryjnego zakończenia aplikacji zmiany mogą zostać utracone.
Do uzyskania instancji SharedPreferences używane są dwie metody: getPreferences i getSharedPreferences. Pierwsza jest dostępna tylko wewnątrz Activity i tworzy plik o nazwie Activity. Druga — bardziej elastyczna, przyjmuje nazwę pliku i tryb dostępu, i jest dostępna z dowolnego kontekstu (Application, Activity, Service). Zaleca się używanie getSharedPreferences z nazwą pliku odpowiadającą modułowi lub funkcjonalności aplikacji.
// Uzyskiwanie SharedPreferences
val prefs = context.getSharedPreferences(
"user_settings", Context.MODE_PRIVATE
)
// Zapisywanie danych
with(prefs.edit()) {
putString("username", "Anna")
putInt("age", 28)
putBoolean("isLoggedIn", true)
apply()
}
// Odczyt danych
val username = prefs.getString("username", "")
val age = prefs.getInt("age", 0)
val isLoggedIn = prefs.getBoolean("isLoggedIn", false)
Przy użyciu MODE_MULTI_PROCESS (deprecated) SharedPreferences synchronizuje się między procesami. Jednak ta synchronizacja nie gwarantuje atomowości i Google zaleca unikanie używania SharedPreferences w scenariuszach multi-process. W takich przypadkach lepiej użyć ContentProvider, Room z dostępem międzyprocesowym lub DataStore.
SharedPreferences udostępnia zestaw metod do odczytu danych po kluczu oraz interfejs Editor do zapisu. Każda metoda odczytu przyjmuje dwa parametry: klucz i wartość domyślną, która jest zwracana, jeśli klucz nie zostanie znaleziony. Wartość domyślna określa również typ zwracanej wartości: getString zwraca String, getInt — Int i tak dalej.
| Metoda odczytu | Metoda zapisu | Typ danych |
|---|---|---|
| getString | putString | String |
| getInt | putInt | Int |
| getBoolean | putBoolean | Boolean |
| getFloat | putFloat | Float |
| getLong | putLong | Long |
| getStringSet | putStringSet | Set<String> |
Editor — to wewnętrzny obiekt SharedPreferences, który gromadzi zmiany w buforze. Po wprowadzeniu wszystkich zmian programista wywołuje commit() (zapis synchroniczny) lub apply() (zapis asynchroniczny). Różnica jest krytyczna: commit blokuje bieżący wątek do pełnego zapisu na dysk i zwraca boolean (sukces/porażka), natomiast apply wykonuje zapis w tle i natychmiast zwraca sterowanie, ale nie zwraca wyniku.
Zaleca się używanie apply zamiast commit we wszystkich przypadkach, gdy nie trzeba znać wyniku zapisu. apply jest szybszy i nie blokuje wątku UI. commit należy stosować tylko wtedy, gdy krytyczne jest poznanie, czy dane zostały zapisane pomyślnie, lub przy pracy z trybem multi-process. Do usuwania pojedynczych kluczy używana jest metoda remove, do całkowitego czyszczenia — clear. Wszystkie operacje usuwania również są wykonywane przez Editor.
// Wiele zmian - jeden apply
prefs.edit {
putString("theme", "dark")
putBoolean("notifications", false)
remove("old_key")
}
// Nasłuchiwanie zmian wartości
prefs.registerOnSharedPreferenceChangeListener { prefs, key ->
Log.d("TAG", "Zmieniono klucz: $key")
}
Od Androida 12 (API 31), SharedPreferences została uzupełniona o wsparcie dla registerOnSharedPreferenceChangeListener z automatycznym wypisywaniem się przez Lifecycle. Pozwala to uniknąć wycieków pamięci związanych z zapomnianymi listenerami. W starszych wersjach programista jest zobowiązany do ręcznego wywoływania unregisterOnSharedPreferenceChangeListener w onDestroy lub onStop komponentu.
Mimo szerokiego rozpowszechnienia, SharedPreferences nie jest uniwersalnym rozwiązaniem dla wszystkich scenariuszy przechowywania danych w Androidzie. W zależności od ilości danych, wymagań dotyczących bezpieczeństwa typów i wydajności, Google zaleca różne alternatywy zawarte w Android Jetpack i standardowej bibliotece Androida.
| Rozwiązanie | Kiedy używać | Wady |
|---|---|---|
| SharedPreferences | Niewielkie ustawienia (do 100 kluczy) | Brak bezpieczeństwa typów, synchroniczny odczyt |
| DataStore | Ustawienia średniej złożoności z korutynami | Brak wstecznej kompatybilności poniżej API 14 |
| Room | Dane strukturalne i listy | Zbędny dla 3–5 ustawień |
| EncryptedSharedPreferences | Wrażliwe dane i tokeny | Zależność od AndroidX Security |
DataStore — to biblioteka Android Jetpack, przedstawiona przez Google jako zamiennik SharedPreferences. Oferuje dwa warianty: Preferences DataStore (klucz-wartość, jak SharedPreferences) i Proto DataStore (typowane przechowywanie przez Protocol Buffers). DataStore używa korutyn i Flow do asynchronicznej pracy, gwarantuje bezpieczeństwo typów i automatycznie obsługuje migracje wersji. Google zaleca DataStore dla wszystkich nowych projektów.
Główna zaleta DataStore — asynchroniczność na poziomie API. Wszystkie operacje odczytu zwracają Flow, a operacje zapisu są funkcjami suspend. To całkowicie eliminuje blokowanie wątku UI, które jest możliwe przy synchronicznym odczycie SharedPreferences. Ponadto DataStore gwarantuje spójność danych: zapis jest wykonywany w transakcji, a w przypadku awarii wszystkie zmiany są wycofywane.
Rozważmy praktyczny przykład: ustawienia motywu (jasny/ciemny/systemowy) w aplikacji na Androida. Użytkownik wybiera motyw, a wybór jest zapisywany w SharedPreferences. Przy kolejnych uruchomieniach aplikacji motyw jest przywracany z zapisanych ustawień. Do reaktywnej aktualizacji interfejsu używane jest obserwowanie zmian przez SharedPreferences.OnSharedPreferenceChangeListener.
Stwórzmy klasę ThemePreferences, która hermetyzuje całą pracę z SharedPreferences dla motywu. Klasa udostępnia metody getTheme (odczyt), setTheme (zapis) i observeTheme (obserwacja). Nazwa pliku ustawień to „app_preferences" z trybem MODE_PRIVATE. Dla wygody klucze są wyodrębnione do companion object jako stałe.
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())
}
}
}
}
W Activity lub Fragment uzyskanie instancji ThemePreferences odbywa się przez kontekst aplikacji. Przy inicjalizacji wywoływany jest getTheme w celu ustawienia aktualnego motywu. Gdy użytkownik wybiera nowy motyw, wywoływany jest setTheme, a poprzez observeTheme interfejs jest aktualizowany bez restartowania Activity. Ważne jest, aby nie zapomnieć o wypisaniu się z listenera w onDestroy, aby zapobiec wyciekowi pamięci, szczególnie jeśli Activity jest odtwarzane przy zmianie konfiguracji.
Dla aplikacji z minimalną docelową wersją Android 12+ zaleca się używanie registerOnSharedPreferenceChangeListener razem z LifecycleObserver. To automatycznie zarządza subskrypcją i wypisywaniem się przy zmianie cyklu życia komponentu. Dla starszych wersji subskrypcja i wypisywanie muszą być zarządzane ręcznie, co jest częstym źródłem błędów w produkcyjnych aplikacjach używających SharedPreferences.
Często zadawane pytania
SharedPreferences bezpośrednio obsługuje tylko typy pierwotne i Set<String>. Do przechowywania obiektów należy zserializować je do JSON przez Gson lub Moshi, zapisać przez putString i zdeserializować przy odczycie. Dla złożonych obiektów z dużą liczbą pól zaleca się używanie Room zamiast SharedPreferences z JSON-serializacją.
Tak, SharedPreferences jest bezpieczny wątkowo. Wszystkie operacje odczytu i zapisu są synchronizowane na poziomie obiektu SharedPreferences i jego Editora. Jednak przy użyciu trybu multi-process synchronizacja nie jest gwarantowana. Dla współbieżnego dostępu z wielu wątków w obrębie jednej aplikacji SharedPreferences jest bezpieczny bez dodatkowych blokad.
Aby całkowicie wyczyścić wszystkie dane z SharedPreferences, wywołaj metodę clear() na Editorze i zastosuj zmiany przez apply. Jeśli chcesz usunąć sam plik XML, użyj deleteSharedPreferences(name) na kontekście. Czyszczenie danych aplikacji przez Ustawienia → Aplikacje → Wyczyść dane również usuwa wszystkie pliki SharedPreferences.
Dla nowych projektów Google zaleca DataStore jako zamiennik SharedPreferences. DataStore zapewnia asynchroniczną pracę z korutynami, bezpieczeństwo typów (Proto DataStore) i automatyczne migracje. SharedPreferences warto wybierać tylko dla projektów z minimalną wersją poniżej API 14 lub gdy potrzebna jest szybka integracja bez dodatkowych zależności.
Do szyfrowania danych używaj EncryptedSharedPreferences z biblioteki AndroidX Security. Automatycznie szyfruje klucze i wartości za pomocą AES-256 GCM. Proces konfiguracji jest minimalny: getSharedPreferences zastępuje się EncryptedSharedPreferences.create z podaniem klucza głównego z Android Keystore.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również