DataStore Teil von Android Jetpack.
Jetpack DataStore ist eine Datenspeicherlösung, mit der Sie Schlüssel/Wert-Paare oder typisierte Objekte mit Protokoll-Buffers speichern können. DataStore verwendet Kotlin-Coroutinen und Flow, um Daten asynchron, konsistent und transaktional zu speichern.
Wenn Sie derzeit 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.
- Preferences DataStore speichert Daten mithilfe von Schlüsseln und greift darauf zu. Für diese Implementierung ist kein vordefiniertes Schema erforderlich und sie bietet keine Typsicherheit.
- Proto DataStore speichert Daten als Instanzen eines benutzerdefinierten Datentyps. Bei dieser Implementierung müssen Sie ein Schema mit Protokollzwischenspeichern definieren, aber es bietet Typsicherheit.
DataStore richtig verwenden
Beachten Sie die folgenden Regeln, um DataStore richtig zu verwenden:
Erstelle niemals mehr als eine Instanz von
DataStore
für eine bestimmte Datei im selben Prozess. Dadurch können alle DataStore-Funktionen beeinträchtigt werden. Wenn für eine bestimmte Datei im selben Prozess mehrere DataStores aktiv sind, wirft DataStore beim Lesen oder Aktualisieren von DatenIllegalStateException
.Der generische Typ des DataStore
muss unveränderlich sein. Wenn Sie einen in DataStore verwendeten Typ mutieren, werden alle von DataStore bereitgestellten Garantien ungültig und es entstehen potenziell schwer zu findende Fehler. Wir empfehlen dringend, Protokoll-Buffers zu verwenden, die für Unveränderlichkeit sorgen, eine einfache API bieten und eine effiziente Serialisierung ermöglichen.Verwenden Sie
SingleProcessDataStore
undMultiProcessDataStore
niemals gleichzeitig für dieselbe Datei. Wenn Sie aus mehreren Prozessen auf dieDataStore
zugreifen möchten, verwenden Sie immerMultiProcessDataStore
.
Einrichten
Wenn Sie Jetpack DataStore in Ihrer App verwenden möchten, fügen Sie Ihrer Gradle-Datei je nach gewünschter Implementierung Folgendes hinzu:
Einstellungen-Datenspeicher
Cool
// 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") }
Proto DataStore
Cool
// 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") }
Schlüssel/Wert-Paare im Datenspeicher für Einstellungen speichern
Die DataStore-Implementierung für die Einstellungen verwendet die Klassen DataStore
und Preferences
, um einfache Schlüssel/Wert-Paare auf der Festplatte zu speichern.
Datenspeicher für Einstellungen erstellen
Verwenden Sie den Property-Delegierten, der mit preferencesDataStore
erstellt wurde, um eine Instanz von DataStore<Preferences>
zu erstellen. Rufen Sie sie einmal auf oberster Ebene Ihrer Kotlin-Datei auf und greifen Sie in der restlichen Anwendung über diese Property darauf zu. So lässt sich DataStore
leichter als Singleton beibehalten. Alternativ können Sie RxPreferenceDataStoreBuilder
verwenden, wenn Sie RxJava verwenden. Der obligatorische Parameter name
ist der Name des Datenspeichers für die Einstellungen.
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();
Aus einem DataStore für Einstellungen lesen
Da im Preferences DataStore kein vordefiniertes Schema verwendet wird, 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. Wenn Sie beispielsweise einen Schlüssel für einen Ganzzahlwert definieren möchten, verwenden Sie intPreferencesKey()
.
Verwenden Sie dann die Property DataStore.data
, um den entsprechenden gespeicherten Wert mit einem Flow
freizugeben.
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 Datenspeicher für Einstellungen schreiben
Der Datenspeicher für Einstellungen bietet die Funktion edit()
, mit der die Daten in einem DataStore
transaktional aktualisiert werden. Der Parameter transform
der Funktion akzeptiert einen Codeblock, in dem Sie die Werte nach Bedarf aktualisieren können. Der gesamte Code im Transformierungsblock wird als 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 Protokoll-Buffer, um typisierte Objekte auf dem Laufwerk zu speichern.
Schema definieren
Für Proto DataStore ist ein vordefiniertes Schema in einer Proto-Datei im Verzeichnis app/src/main/proto/
erforderlich. In diesem Schema wird der Typ der Objekte definiert, die Sie in Ihrem Proto DataStore speichern. Weitere Informationen zum Definieren eines Proto-Schemas finden Sie im Leitfaden zur Protobuf-Sprache.
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
Proto-Datenspeicher erstellen
Es sind zwei Schritte erforderlich, um einen Proto DataStore zum Speichern Ihrer typisierten Objekte zu erstellen:
- Definieren Sie eine Klasse, die
Serializer<T>
implementiert, wobeiT
der in der Protodatei definierte Typ ist. Diese Serializer-Klasse gibt DataStore an, wie der Datentyp gelesen und geschrieben werden soll. Geben Sie einen Standardwert für den Serializer an, der verwendet werden soll, wenn noch keine Datei erstellt wurde. - Verwenden Sie den von
dataStore
erstellten Property Delegate, um eine Instanz vonDataStore<T>
zu erstellen. Dabei istT
der in der Protodatei definierte Typ. Rufen Sie diese Funktion einmal auf oberster Ebene Ihrer Kotlin-Datei auf und greifen Sie in der restlichen App über diesen Property Delegate darauf zu. Der Parameterfilename
gibt DataStore an, welche Datei zum Speichern der Daten verwendet werden soll, und der Parameterserializer
gibt DataStore 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() { 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();
Aus einem Proto DataStore lesen
Mit DataStore.data
kannst du einen Flow
der entsprechenden Property aus deinem gespeicherten Objekt freigeben.
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 eine updateData()
-Funktion, mit der ein gespeichertes Objekt transaktionsweise aktualisiert wird. updateData()
gibt 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 nicht immer möglich, den umgebenden Code asynchron zu ändern. Das kann der Fall sein, wenn Sie mit einer vorhandenen Codebasis arbeiten, die synchrone Laufwerk-E/A verwendet, oder wenn Sie eine Abhängigkeit haben, die keine asynchrone API bereitstellt.
Kotlin-Goroutinen bieten den runBlocking()
-Goroutinen-Builder, um die Lücke zwischen synchronem und asynchronem Code zu schließen. Mit runBlocking()
können Sie Daten synchron aus Datastore lesen.
RxJava bietet Blockierungsmethoden für Flowable
. Im folgenden Code wird der aufrufende Thread blockiert, bis DataStore Daten zurückgibt:
Kotlin
val exampleData = runBlocking { context.dataStore.data.first() }
Java
Settings settings = dataStore.data().blockingFirst();
Die Ausführung synchroner E/A-Vorgänge im UI-Thread kann zu ANRs oder Rucklern der Benutzeroberfläche führen. Sie können diese Probleme vermeiden, indem Sie die Daten aus dem DataStore asynchron vorladen:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { context.dataStore.data.first() // You should also handle IOExceptions here. } }
Java
dataStore.data().first().subscribe();
So liest DataStore die Daten asynchron und speichert sie im Arbeitsspeicher. Spätere synchrone Lesevorgänge mit runBlocking()
können schneller sein oder eine Laufwerk-I/O-Operation vollständig vermeiden, wenn die anfängliche 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, wobei die Datenkonsistenz genauso gewährleistet ist wie innerhalb eines einzelnen Prozesses. Insbesondere garantiert DataStore Folgendes:
- Bei Lesezugriffen werden nur die Daten zurückgegeben, die auf dem Laufwerk gespeichert wurden.
- Konsistenz beim Lesen nach dem Schreiben.
- 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) } } }
Die App würde diese Änderungen erfassen und die Benutzeroberfläche aktualisieren.
val settings: Settings by dataStore.data.collectAsState() Text( text = "Last updated: $${settings.timestamp}", )
Damit DataStore in verschiedenen Prozessen verwendet werden kann, müssen Sie das DataStore-Objekt mithilfe der MultiProcessDataStoreFactory
erstellen.
val dataStore: DataStore<Settings> = MultiProcessDataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
}
)
serializer
gibt DataStore an, wie der Datentyp gelesen und geschrieben werden soll.
Geben Sie einen Standardwert für den Serializer an, der verwendet werden soll, wenn noch keine Datei erstellt wurde. Unten 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): 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()
)
}
}
Mit der Hilt-Abhängigkeitsinjektion können Sie dafür sorgen, dass 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 von DataStore auf dem Laufwerk beschädigt werden. Standardmäßig wird DataStore bei Beschädigung nicht automatisch wiederhergestellt. Versuche, Daten daraus zu lesen, führen dazu, dass das System eine CorruptionException
auslöst.
DataStore bietet eine API für den Umgang mit Beschädigungen, mit der Sie in einem solchen Fall eine ordnungsgemäße Wiederherstellung durchführen und das Auslösen der Ausnahme vermeiden können. Wenn der Datenbeschädigungs-Handler konfiguriert ist, ersetzt er die beschädigte Datei durch eine neue Datei mit einem vordefinierten Standardwert.
Wenn Sie diesen Handler einrichten möchten, geben Sie beim Erstellen der DataStore-Instanz in by dataStore()
oder in der DataStoreFactory
-Factorymethode 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
Du kannst uns dein Feedback und deine Ideen über die folgenden Ressourcen mitteilen:
- Issue Tracker
- Melden Sie Probleme, damit wir Fehler beheben können.
Weitere Informationen
Weitere Informationen zu Jetpack DataStore finden Sie in den folgenden Ressourcen:
Produktproben
Blogs
Codelabs
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Seitenbasierte Daten laden und anzeigen
- LiveData – Übersicht
- Layouts und Bindungsausdrücke