Project: /architecture/_project.yaml Book: /architecture/_book.yaml keywords: datastore, architecture, api:JetpackDataStore description: Explore this app architecture guide on data layer libraries to learn about Preferences DataStore and Proto DataStore, Setup, and more. hide_page_heading: true
DataStore Teil von Android Jetpack.
Jetpack DataStore ist eine Datenspeicherlösung, mit der Sie Schlüssel/Wert-Paare oder typisierte Objekte mit Protocol Buffers speichern können. DataStore verwendet Kotlin-Coroutinen und Flow, um Daten asynchron, konsistent und transaktional zu speichern.
Wenn Sie SharedPreferences
zum Speichern von Daten verwenden, sollten Sie stattdessen zu DataStore migrieren.
Preferences DataStore und Proto DataStore
DataStore bietet zwei verschiedene Implementierungen: Preferences DataStore und Proto DataStore.
- Im Preferences DataStore werden Daten mithilfe von Schlüsseln gespeichert und abgerufen. Für diese Implementierung ist kein vordefiniertes Schema erforderlich und sie bietet keine Typsicherheit.
- In Proto DataStore werden Daten als Instanzen eines benutzerdefinierten Datentyps gespeichert. Bei dieser Implementierung müssen Sie ein Schema mit Protokollzwischenspeichern definieren. Sie bietet jedoch Typsicherheit.
DataStore richtig verwenden
Damit Sie DataStore richtig verwenden, sollten Sie immer die folgenden Regeln beachten:
Erstellen Sie niemals mehr als eine Instanz von
DataStore
für eine bestimmte Datei im selben Prozess. Dadurch kann die gesamte DataStore-Funktionalität beeinträchtigt werden. Wenn für eine bestimmte Datei im selben Prozess mehrere DataStores aktiv sind, wird beim Lesen oder Aktualisieren von Daten eineIllegalStateException
ausgelöst.Der generische Typ von
DataStore<T>
muss unveränderlich sein. Wenn Sie einen Typ ändern, der in DataStore verwendet wird, wird die von DataStore bereitgestellte Konsistenz ungültig und es können potenziell schwerwiegende, schwer zu erkennende Fehler auftreten. Wir empfehlen die Verwendung von Protocol Buffers, da sie für Unveränderlichkeit, eine übersichtliche API und eine effiziente Serialisierung sorgen.Verwenden Sie nicht gleichzeitig
SingleProcessDataStore
undMultiProcessDataStore
für dieselbe Datei. Wenn Sie von mehr als einem Prozess aufDataStore
zugreifen möchten, müssen SieMultiProcessDataStore
verwenden.
Einrichten
Wenn Sie Jetpack DataStore in Ihrer App verwenden möchten, fügen Sie Ihrer Gradle-Datei je nach gewünschter Implementierung Folgendes hinzu:
Preferences DataStore
Groovy
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation "androidx.datastore:datastore-preferences:1.1.7" // optional - RxJava2 support implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.7" // optional - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.7" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-preferences-core:1.1.7" }
Kotlin
// Preferences DataStore (SharedPreferences like APIs) dependencies { implementation("androidx.datastore:datastore-preferences:1.1.7") // optional - RxJava2 support implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.7") // optional - RxJava3 support implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.7") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-preferences-core:1.1.7") }
Proto DataStore
Groovy
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation "androidx.datastore:datastore:1.1.7" // optional - RxJava2 support implementation "androidx.datastore:datastore-rxjava2:1.1.7" // optional - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.1.7" } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation "androidx.datastore:datastore-core:1.1.7" }
Kotlin
// Typed DataStore (Typed API surface, such as Proto) dependencies { implementation("androidx.datastore:datastore:1.1.7") // optional - RxJava2 support implementation("androidx.datastore:datastore-rxjava2:1.1.7") // optional - RxJava3 support implementation("androidx.datastore:datastore-rxjava3:1.1.7") } // Alternatively - use the following artifact without an Android dependency. dependencies { implementation("androidx.datastore:datastore-core:1.1.7") }
Schlüssel/Wert-Paare mit Preferences DataStore speichern
Die Preferences DataStore-Implementierung verwendet die Klassen DataStore
und Preferences
, um Schlüssel/Wert-Paare auf der Festplatte zu speichern.
Preferences DataStore erstellen
Verwenden Sie den von preferencesDataStore
erstellten Property-Delegaten, um eine Instanz von DataStore<Preferences>
zu erstellen. Rufen Sie sie einmal auf der obersten Ebene Ihrer Kotlin-Datei auf und greifen Sie im Rest Ihrer Anwendung über diese Eigenschaft darauf zu. So lässt sich Ihr DataStore
leichter als Singleton beibehalten.
Wenn Sie RxJava verwenden, können Sie stattdessen RxPreferenceDataStoreBuilder
verwenden.
Der obligatorische Parameter name
ist der Name des Preferences 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, "settings").build();
Aus einem Preferences DataStore lesen
Da Preferences DataStore kein vordefiniertes Schema verwendet, müssen Sie mit der entsprechenden Funktion für den Schlüsseltyp einen Schlüssel für jeden Wert definieren, den Sie in der DataStore<Preferences>
-Instanz speichern möchten. Um beispielsweise einen Schlüssel für einen Integer-Wert zu definieren, verwenden Sie intPreferencesKey()
. Verwenden Sie dann die Eigenschaft DataStore.data
, um den entsprechenden gespeicherten Wert über ein Flow
verfügbar zu machen.
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));
In einen Preferences DataStore schreiben
Preferences DataStore bietet die Funktion edit()
, mit der die Daten in einem DataStore
transaktional aktualisiert werden. Der transform
-Parameter der Funktion akzeptiert einen Codeblock, in dem Sie die Werte nach Bedarf aktualisieren können. Der gesamte Code im Transform-Block wird als eine einzelne Transaktion behandelt.
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.
Typisierte Objekte mit Proto DataStore speichern
Die Proto DataStore-Implementierung verwendet DataStore und Protobufs, um typisierte Objekte auf der Festplatte zu speichern.
Schema definieren
Für Proto DataStore ist ein vordefiniertes Schema in einer Protobuf-Datei im Verzeichnis app/src/main/proto/
erforderlich. Dieses Schema definiert den Typ für die Objekte, die Sie in Ihrem Proto DataStore speichern. Weitere Informationen zum Definieren eines Proto-Schemas finden Sie im Sprachleitfaden für Protokollpuffer.
syntax = "proto3";
option java_package = "com.example.application.proto";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
Proto DataStore erstellen
Es gibt zwei Schritte zum Erstellen eines Proto DataStore zum Speichern Ihrer typisierten Objekte:
- Definieren Sie eine Klasse, die
Serializer<T>
implementiert, wobeiT
der in der Proto-Datei definierte Typ ist. Diese Serializer-Klasse teilt DataStore mit, wie Ihr Datentyp gelesen und geschrieben werden soll. Achten Sie darauf, dass Sie einen Standardwert für den Serializer angeben, der verwendet werden soll, wenn noch keine Datei erstellt wurde. - Verwenden Sie den von
dataStore
erstellten Attribut-Delegate, um eine Instanz vonDataStore<T>
zu erstellen, wobeiT
der in der Proto-Datei definierte Typ ist. Rufen Sie diese einmal auf der obersten Ebene Ihrer Kotlin-Datei auf und greifen Sie über diesen Eigenschafts-Delegate im Rest Ihrer App darauf zu. Der Parameterfilename
gibt an, welche Datei DataStore zum Speichern der Daten verwenden soll, und der Parameterserializer
gibt den Namen der in Schritt 1 definierten Serializer-Klasse an.
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() {
return Settings.getDefaultInstance();
}
@Override
public Settings readFrom(@NotNull InputStream input) {
try {
return Settings.parseFrom(input);
} catch (InvalidProtocolBufferException exception) {
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();
Aus einem Proto DataStore lesen
Verwenden Sie DataStore.data
, um eine Flow
der entsprechenden Eigenschaft aus Ihrem gespeicherten Objekt verfügbar zu machen.
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());
In einen Proto DataStore schreiben
Proto DataStore bietet die Funktion updateData()
, mit der ein gespeichertes Objekt transaktional aktualisiert wird. updateData()
gibt Ihnen den aktuellen Status der Daten als Instanz Ihres Datentyps zurück und aktualisiert die Daten transaktional in einem atomaren Lese-/Schreib-/Änderungsvorgang.
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()));
DataStore in synchronem Code verwenden
Einer der Hauptvorteile von DataStore ist die asynchrone API. Es ist jedoch möglicherweise nicht immer möglich, den umgebenden Code so zu ändern, dass er asynchron ist. Das kann der Fall sein, wenn Sie mit einer vorhandenen Codebasis arbeiten, die synchrone Festplatten-E/A verwendet, oder wenn Sie eine Abhängigkeit haben, die keine asynchrone API bietet.
Kotlin-Coroutinen bieten den Coroutinen-Builder runBlocking()
, um die Lücke zwischen synchronem und asynchronem Code zu schließen. Mit runBlocking()
können Sie Daten synchron aus DataStore lesen. RxJava bietet blockierende Methoden für Flowable
. Der folgende Code blockiert den aufrufenden Thread, bis DataStore Daten zurückgibt:
Kotlin
val exampleData = runBlocking { context.dataStore.data.first() }
Java
Settings settings = dataStore.data().blockingFirst();
Wenn synchrone E/A-Vorgänge im UI-Thread ausgeführt werden, kann dies zu ANR-Fehlern oder einer nicht reagierenden Benutzeroberfläche führen. Sie können diese Probleme umgehen, indem Sie die Daten aus DataStore asynchron vorab laden:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
context.dataStore.data.first()
// You should also handle IOExceptions here.
}
}
Java
dataStore.data().first().subscribe();
Auf diese Weise liest DataStore die Daten asynchron und speichert sie im Cache. Spätere synchrone Lesevorgänge mit runBlocking()
können schneller sein oder eine Datenträger-E/A-Operation ganz vermeiden, wenn der erste Lesevorgang abgeschlossen ist.
DataStore in Code mit mehreren Prozessen verwenden
Sie können DataStore so konfigurieren, dass in verschiedenen Prozessen auf dieselben Daten zugegriffen wird, mit denselben Datenkonsistenzeigenschaften wie in einem einzelnen Prozess. DataStore bietet insbesondere Folgendes:
- Bei Lesevorgängen werden nur die Daten zurückgegeben, die auf der Festplatte gespeichert wurden.
- Read-After-Write-Konsistenz.
- Schreibvorgänge werden serialisiert.
- Lesevorgänge werden nie durch Schreibvorgänge blockiert.
Betrachten Sie eine Beispielanwendung mit einem Dienst und einer Aktivität:
Der Dienst wird in einem separaten Prozess ausgeführt und aktualisiert den DataStore regelmäßig.
<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) } } }
Während die App diese Änderungen erfasst und die Benutzeroberfläche aktualisiert
val settings: Settings by dataStore.data.collectAsState() Text( text = "Last updated: $${settings.timestamp}", )
Damit Sie DataStore in verschiedenen Prozessen verwenden können, müssen Sie das DataStore-Objekt mit dem MultiProcessDataStoreFactory
erstellen.
val dataStore: DataStore<Settings> = MultiProcessDataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
}
)
serializer
gibt DataStore an, wie Ihr Datentyp gelesen und geschrieben werden soll. Achten Sie darauf, dass Sie einen Standardwert für den Serializer angeben, der verwendet werden soll, wenn noch keine Datei erstellt wurde. Im Folgenden finden Sie ein Beispiel für die Implementierung mit 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): Settings =
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()
)
}
}
Sie können die Hilt-Abhängigkeitsinjektion verwenden, damit Ihre DataStore-Instanz pro Prozess eindeutig ist:
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
MultiProcessDataStoreFactory.create(...)
Umgang mit beschädigten Dateien
In seltenen Fällen kann die persistente Datei auf der Festplatte von DataStore beschädigt werden. Standardmäßig wird DataStore nicht automatisch nach einer Beschädigung wiederhergestellt. Versuche, Daten daraus zu lesen, führen dazu, dass das System eine CorruptionException
auslöst.
DataStore bietet eine API für die Verarbeitung von Beschädigungen, mit der Sie sich in einem solchen Fall ordnungsgemäß erholen und das Auslösen der Ausnahme vermeiden können. Wenn der Handler für beschädigte Dateien konfiguriert ist, ersetzt er die beschädigte Datei durch eine neue Datei mit einem vordefinierten Standardwert.
Um diesen Handler einzurichten, geben Sie beim Erstellen der DataStore-Instanz in by dataStore()
oder in der Factory-Methode DataStoreFactory
eine corruptionHandler
an:
val dataStore: DataStore<Settings> = DataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
},
corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)
Feedback geben
Über diese Ressourcen können Sie uns Ihr Feedback und Ihre Ideen mitteilen:
- Problemverfolgung:
- Probleme melden, damit wir Fehler beheben können.
Zusätzliche Ressourcen
Weitere Informationen zu Jetpack DataStore finden Sie in den folgenden zusätzlichen Ressourcen:
Produktproben
Blogs
Codelabs
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Paginierte Daten laden und anzeigen
- LiveData – Übersicht
- Layouts und Bindungsausdrücke