Magazyn danych Zawiera Android Jetpack.
Jetpack DataStore to rozwiązanie do przechowywania danych, które umożliwia przechowywanie par klucz-wartość. pary lub wpisane obiekty z protokołem bufory. DataStore używa platformy Kotlin i przepływu do asynchronicznego, spójnego przechowywania danych transakcji.
Jeśli obecnie używasz
SharedPreferences
do
, rozważ migrację do DataStore.
Preferencje DataStore i Proto DataStore
DataStore udostępnia 2 różne implementacje: Preferences DataStore oraz Proto DataStore.
- Preferences DataStore przechowuje dane i uzyskuje do nich dostęp za pomocą kluczy. Ten nie wymaga wstępnie zdefiniowanego schematu i nie zapewnia bezpieczeństwa pisania.
- Proto DataStore przechowuje dane jako instancje danych niestandardowego typu. Ten wdrożenie wymaga zdefiniowania schematu za pomocą protokołu buforuje, ale podaje typ bezpieczeństwa.
Prawidłowe korzystanie z DataStore
Aby poprawnie korzystać z DataStore, pamiętaj o tych regułach:
Nigdy nie twórz więcej niż jednego wystąpienia ciągu
DataStore
dla danego pliku w ten sam proces. Może to zakłócić działanie wszystkich funkcji DataStore. Jeśli wiele magazynów DataStore aktywnych dla danego pliku w tym samym procesie, DataStore zgłasza funkcjęIllegalStateException
podczas odczytywania lub aktualizowania danych.Ogólny typ magazynu DataStore
musi być stały. Mutacja typu używane w DataStore unieważniają wszelkie gwarancje udzielane i tworzone przez DataStore potencjalnie poważnych i trudnych do naprawienia błędy. Zdecydowanie zalecamy użycie bufory protokołów, które zapewniają gwarancje niezmienności, prosty interfejs API wydajną serializację.Nigdy nie mieszaj użycia funkcji
SingleProcessDataStore
iMultiProcessDataStore
w tym samym pliku. Jeśli chcesz uzyskać dostęp do usługiDataStore
z więcej niż jednego zawsze używajMultiProcessDataStore
.
Konfiguracja
Aby korzystać z Jetpack DataStore w swojej aplikacji, dodaj do pliku Gradle poniższy kod w zależności od tego, której implementacji chcesz użyć:
Preferencje DataStore
Odlotowe
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation "androidx.datastore:datastore-preferences:1.1.1" // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.1" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-preferences-core:1.1.1" }
Kotlin
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation("androidx.datastore:datastore-preferences:1.1.1") // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.1") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-preferences-core:1.1.1") }
Magazyn danych Proto
Odlotowe
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation "androidx.datastore:datastore:1.1.1" // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.1.1" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.1.1" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-core:1.1.1" }
Kotlin
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation("androidx.datastore:datastore:1.1.1") // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.1.1") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.1.1") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-core:1.1.1") }
Przechowywanie par klucz-wartość w Preferences DataStore
Implementacja preferencji DataStore korzysta z metody
DataStore
i
Preferences
zachowywanie prostych par klucz-wartość na dysku.
Tworzenie magazynu danych preferencji
Użyj przedstawiciela usługi utworzonego przez użytkownika preferencesDataStore
, aby utworzyć instancję Datastore<Preferences>
. Wywołuj je raz na najwyższym poziomie pliku kotlin. Uzyskuj do niego dostęp przez tę usługę przez pozostałą część aplikacji. Dzięki temu łatwiej Ci będzie zadbać o to, żeby DataStore
był Tobą w pojedynkę. Możesz też użyć narzędzia RxPreferenceDataStoreBuilder
w przypadku używania biblioteki RxJava. Obowiązkowy parametr name
to nazwa
Ustawienia DataStore.
Kotlin
// At the top level of your kotlin file: val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
Java
RxDataStore<Preferences> dataStore = new RxPreferenceDataStoreBuilder(context, /*name=*/ "settings").build();
Odczyt z preferencji DataStore
Preferencje DataStore nie korzystają ze wstępnie zdefiniowanego schematu, więc musisz użyć metody
odpowiednią funkcję typu klucza, aby zdefiniować klucz dla każdej wartości, którą musisz
w instancji DataStore<Preferences>
. Aby na przykład zdefiniować klucz
dla wartości całkowitej, użyj funkcji
intPreferencesKey()
Następnie skorzystaj z
Usługa DataStore.data
aby udostępnić odpowiednią wartość przedpłaconą za pomocą funkcji Flow
.
Kotlin
val EXAMPLE_COUNTER = intPreferencesKey("example_counter") val exampleCounterFlow: Flow<Int> = context.dataStore.data .map { preferences -> // No type safety. preferences[EXAMPLE_COUNTER] ?: 0 }
Java
Preferences.Key<Integer> EXAMPLE_COUNTER = PreferencesKeys.int("example_counter"); Flowable<Integer> exampleCounterFlow = dataStore.data().map(prefs -> prefs.get(EXAMPLE_COUNTER));
Zapisz w preferencji DataStore
Preferencje DataStore zapewnia
edit()
która transakcyjna aktualizuje dane w formacie DataStore
. Parametr funkcji
Parametr transform
akceptuje blok kodu, w którym możesz aktualizować wartości jako
niezbędną. Cały kod w bloku przekształcenia jest traktowany jako pojedynczy kod
transakcji.
Kotlin
suspend fun incrementCounter() { context.dataStore.edit { settings -> val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0 settings[EXAMPLE_COUNTER] = currentCounterValue + 1 } }
Java
Single<Preferences> updateResult = dataStore.updateDataAsync(prefsIn -> { MutablePreferences mutablePreferences = prefsIn.toMutablePreferences(); Integer currentInt = prefsIn.get(INTEGER_KEY); mutablePreferences.set(INTEGER_KEY, currentInt != null ? currentInt + 1 : 1); return Single.just(mutablePreferences); }); // The update is completed once updateResult is completed.
Przechowuj obiekty wpisywane za pomocą Proto DataStore
Implementacja Proto DataStore korzysta z DataStore i protokołu bufory, aby były zapisywane obiektów na dysk.
Zdefiniuj schemat
Proto DataStore wymaga wstępnie zdefiniowanego schematu w pliku proto w
Katalog app/src/main/proto/
. Ten schemat określa typ obiektów
które pozostają w magazynie danych Proto. Aby dowiedzieć się więcej o definiowaniu protokołu
schema.org, patrz język protokołu Protobuf
.
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
Utwórz magazyn danych Proto
Tworzenie bazy danych Proto do przechowywania wpisywanych danych wymaga wykonania 2 etapów obiekty:
- Zdefiniuj klasę, która stosuje metodę
Serializer<T>
, gdzieT
jest typem zdefiniowanym w pliku proto. Ta klasa serializowania informuje DataStore, jak odczytywać i zapisywać typ danych. Pamiętaj, aby podać wartość domyślną dla serializatora jest używana, jeśli nie utworzono jeszcze żadnego pliku. - Użyj przedstawiciela właściwości utworzonego przez użytkownika
dataStore
, aby utworzyć instancję argumentuDataStore<T>
, gdzieT
to typ zdefiniowany w pliku proto. Zadzwoń na najwyższym poziomie pliku kotlin i uzyskać do niego dostęp za pośrednictwem tej usługi. w dalszej części aplikacji. Parametrfilename
informuje DataStore, w którym plik ma być przechowywany dane, oraz parametrserializer
informuje DataStore nazwę klasy serializatora zdefiniowane w kroku 1.
Kotlin
object SettingsSerializer : Serializer<Settings> { override val defaultValue: Settings = Settings.getDefaultInstance() override suspend fun readFrom(input: InputStream): Settings { try { return Settings.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) } } override suspend fun writeTo( t: Settings, output: OutputStream) = t.writeTo(output) } val Context.settingsDataStore: DataStore<Settings> by dataStore( fileName = "settings.pb", serializer = SettingsSerializer )
Java
private static class SettingsSerializer implements Serializer<Settings> { @Override public Settings getDefaultValue() { Settings.getDefaultInstance(); } @Override public Settings readFrom(@NotNull InputStream input) { try { return Settings.parseFrom(input); } catch (exception: InvalidProtocolBufferException) { throw CorruptionException(“Cannot read proto.”, exception); } } @Override public void writeTo(Settings t, @NotNull OutputStream output) { t.writeTo(output); } } RxDataStore<Byte> dataStore = new RxDataStoreBuilder<Byte>(context, /* fileName= */ "settings.pb", new SettingsSerializer()).build();
Odczyt z Proto DataStore
Użyj metody DataStore.data
, aby udostępnić właściwość Flow
odpowiedniej właściwości z przechowywanego obiektu.
Kotlin
val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data .map { settings -> // The exampleCounter property is generated from the proto schema. settings.exampleCounter }
Java
Flowable<Integer> exampleCounterFlow = dataStore.data().map(settings -> settings.getExampleCounter());
Zapisz w Proto DataStore
Proto DataStore zapewnia
updateData()
która transakcyjnie aktualizuje zapisany obiekt. updateData()
daje Ci
bieżącego stanu danych jako instancji typu danych i aktualizuje
danych transakcyjnych w ramach niepodzielnej operacji odczytu, zapisu i modyfikacji.
Kotlin
suspend fun incrementCounter() { context.settingsDataStore.updateData { currentSettings -> currentSettings.toBuilder() .setExampleCounter(currentSettings.exampleCounter + 1) .build() } }
Java
Single<Settings> updateResult = dataStore.updateDataAsync(currentSettings -> Single.just( currentSettings.toBuilder() .setExampleCounter(currentSettings.getExampleCounter() + 1) .build()));
Użyj DataStore w kodzie synchronicznym
Jedną z głównych zalet DataStore jest asynchroniczny interfejs API, zawsze istnieje możliwość zmiany otaczającego kodu na asynchroniczny. Ten dla osób, które pracują z dotychczasową bazą kodu, która używa synchronicznego wejścia/wyjścia dysku lub jeśli istnieje zależność, która nie udostępnia do asynchronicznego interfejsu API.
Współrzędy Kotlina zapewniają
runBlocking()
narzędzie do tworzenia współzależności, które pomaga wypełnić lukę między synchroniczną i asynchroniczną
w kodzie. Za pomocą runBlocking()
możesz synchronicznie odczytywać dane z DataStore.
RxJava oferuje metody blokowania w przeglądarce Flowable
. Wywołanie blokuje poniższy kod
do momentu zwrócenia danych:
Kotlin
val exampleData = runBlocking { context.dataStore.data.first() }
Java
Settings settings = dataStore.data().blockingFirst();
Wykonywanie synchronicznych operacji wejścia-wyjścia w wątku interfejsu użytkownika może spowodować Błędy ANR lub zacinanie się interfejsu. Aby rozwiązać te problemy, możesz asynchronicznie wstępnie wczytać dane z DataStore:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { context.dataStore.data.first() // You should also handle IOExceptions here. } }
Java
dataStore.data().first().subscribe();
Dzięki temu DataStore asynchronicznie odczytuje dane i zapisuje je w pamięci podręcznej. Później
odczyty synchroniczne z użyciem funkcji runBlocking()
mogą być szybsze lub mogą unikać wejścia/wyjścia dysku
po zakończeniu początkowego odczytu.
Użyj DataStore w kodzie wieloprocesowym
Możesz skonfigurować magazyn danych tak, aby uzyskiwać dostęp do tych samych danych w różnych procesach z takimi samymi gwarancjami spójności danych, jak w ramach jednego procesu. W DataStore gwarantuje, że:
- Odczyty zwracają tylko dane, które zostały utrwalone na dysku.
- Spójność odczytu po zapisie.
- Zapisy są serializowane.
- Odczyty nigdy nie są blokowane przez zapisy.
Rozważ przykładową aplikację z usługą i działaniem:
Usługa działa w oddzielnym procesie i okresowo aktualizuje Magazyn danych
<service android:name=".MyService" android:process=":my_process_id" />
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { scope.launch { while(isActive) { dataStore.updateData { Settings(lastUpdate = System.currentTimeMillis()) } delay(1000) } } }
Aplikacja zbiera te zmiany i aktualizuje interfejs użytkownika
val settings: Settings by dataStore.data.collectAsState() Text( text = "Last updated: $${settings.timestamp}", )
Aby używać magazynu danych w różnych procesach, musisz utworzyć
obiekt DataStore za pomocą interfejsu MultiProcessDataStoreFactory
.
val dataStore: DataStore<Settings> = MultiProcessDataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
}
)
serializer
informuje DataStore, jak odczytywać i zapisywać typ danych.
Pamiętaj, aby podać wartość domyślną dla serializatora, która ma być używana, jeśli
nie utworzono jeszcze żadnego pliku. Poniżej znajduje się przykład użycia funkcji
kotlinx.serialization:
@Serializable
data class Settings(
val lastUpdate: Long
)
@Singleton
class SettingsSerializer @Inject constructor() : Serializer<Settings> {
override val defaultValue = Settings(lastUpdate = 0)
override suspend fun readFrom(input: InputStream): Timer =
try {
Json.decodeFromString(
Settings.serializer(), input.readBytes().decodeToString()
)
} catch (serialization: SerializationException) {
throw CorruptionException("Unable to read Settings", serialization)
}
override suspend fun writeTo(t: Settings, output: OutputStream) {
output.write(
Json.encodeToString(Settings.serializer(), t)
.encodeToByteArray()
)
}
}
Możesz użyć zależności Hilt wstrzyknięcie, aby zapewnić niepowtarzalność instancji DataStore dla każdego procesu:
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
MultiProcessDataStoreFactory.create(...)
Prześlij opinię
Podziel się z nami swoimi opiniami i pomysłami, korzystając z tych zasobów:
- Narzędzie do śledzenia błędów
- Zgłoś problemy, abyśmy mogli je naprawić.
Dodatkowe materiały
Aby dowiedzieć się więcej o Jetpack DataStore, zapoznaj się z tymi dodatkowymi materiałami:
Próbki
Blogi
Ćwiczenia z programowania
.Polecane dla Ciebie
- Uwaga: tekst linku wyświetla się, gdy JavaScript jest wyłączony
- Wczytywanie i wyświetlanie danych z podziałem na strony
- Omówienie LiveData
- Układy i wyrażenia wiążące