DataStore Teil von Android Jetpack.
Jetpack DataStore ist eine Datenspeicherlösung, mit der Sie Schlüssel/Wert-Paare oder typisierte Objekte mit Protokollzwischenspeichern 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.
Einstellungen für DataStore und Proto DataStore
DataStore bietet zwei verschiedene Implementierungen: Preferred DataStore und Proto DataStore.
- Einstellungen – Datenspeicher speichert Daten und greift mithilfe von Schlüsseln darauf zu. Für diese Implementierung ist kein vordefiniertes Schema erforderlich und sie bietet keine Typensicherheit.
- Proto DataStore speichert Daten als Instanzen eines benutzerdefinierten Datentyps. Für diese Implementierung müssen Sie zwar ein Schema mit Protokollzwischenspeichern definieren, es bietet jedoch Typsicherheit.
DataStore korrekt verwenden
Beachten Sie die folgenden Regeln, um DataStore korrekt verwenden zu können:
Erstellen Sie nie mehr als eine Instanz von
DataStore
pro Datei im selben Prozess. Andernfalls werden alle DataStore-Funktionen beeinträchtigt. Wenn für eine bestimmte Datei im selben Prozess mehrere Datenspeicher aktiv sind, löst DataStore beim Lesen oder Aktualisieren von DatenIllegalStateException
aus.Der generische Typ des Datenspeichers
muss unveränderlich sein. Durch das Ändern eines in DataStore verwendeten Typs werden alle Garantien von DataStore ungültig gemacht und potenziell schwerwiegende, schwer zu erkennende Fehler entstehen. Es wird dringend empfohlen, Protokollpuffer zu verwenden, die Unveränderlichkeitsgarantien, eine einfache API und eine effiziente Serialisierung bieten.Vermischen Sie
SingleProcessDataStore
undMultiProcessDataStore
nie in derselben Datei. Wenn Sie über mehrere Prozesse aufDataStore
zugreifen möchten, verwenden Sie immerMultiProcessDataStore
.
Einrichten
Wenn Sie Jetpack DataStore in Ihrer App verwenden möchten, fügen Sie der Gradle-Datei Folgendes hinzu, je nachdem, welche Implementierung Sie verwenden möchten:
Datenspeicher von Einstellungen
Groovig
// 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-Datenspeicher
Groovig
// 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 mit dem Datastore für Einstellungen speichern
Die Datastore-Implementierung von Preferredes verwendet die Klassen DataStore
und Preferences
, um einfache Schlüssel/Wert-Paare auf dem Laufwerk zu speichern.
Datenspeicher für Einstellungen erstellen
Verwenden Sie den von preferencesDataStore
erstellten Property-Delegaten, um eine Instanz von Datastore<Preferences>
zu erstellen. Rufen Sie es einmal auf der obersten Ebene Ihrer Kotlin-Datei auf und greifen Sie über diese Eigenschaft im Rest Ihrer Anwendung darauf zu. So ist es einfacher, DataStore
als Singleton zu behalten. Alternativ können Sie RxPreferenceDataStoreBuilder
nutzen, wenn Sie RxJava nutzen. Der obligatorische name
-Parameter ist der Name des Einstellungs-DataStores.
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 Einstellungs-Datenspeicher lesen
Da Settings DataStore kein vordefiniertes Schema verwendet, müssen Sie mit der entsprechenden Schlüsseltypfunktion für jeden Wert, den Sie in der Instanz DataStore<Preferences>
speichern müssen, einen Schlüssel definieren. Wenn Sie beispielsweise einen Schlüssel für einen Ganzzahlwert definieren möchten, verwenden Sie intPreferencesKey()
.
Verwenden Sie dann das Attribut DataStore.data
, um den entsprechenden gespeicherten Wert mithilfe von 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 Einstellungs-Datenspeicher schreiben
Einstellungs-DataStore bietet eine edit()
-Funktion, die die Daten in einem DataStore
transaktional aktualisiert. Der Parameter transform
der Funktion akzeptiert einen Codeblock, in dem Sie die Werte nach Bedarf aktualisieren können. Der gesamte Code im Transformationsblock 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 Protokollzwischenspeicher, um typisierte Objekte auf dem Laufwerk zu speichern.
Schema definieren
Proto DataStore erfordert ein vordefiniertes Schema in einer .proto-Datei im Verzeichnis app/src/main/proto/
. Dieses Schema definiert den Typ für die Objekte, die in Ihrem Proto DataStore beibehalten werden. Weitere Informationen zum Definieren eines Proto-Schemas finden Sie im Sprachleitfaden protobuf.
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message Settings {
int32 example_counter = 1;
}
Proto-Datenspeicher erstellen
Zum Erstellen eines Proto DataStore zum Speichern Ihrer typisierten Objekte sind zwei Schritte erforderlich:
- Definieren Sie eine Klasse, die
Serializer<T>
implementiert, wobeiT
der in der .proto-Datei definierte Typ ist. Diese Serialisierungsklasse teilt DataStore mit, wie Ihr Datentyp gelesen und geschrieben wird. Sie müssen einen Standardwert für den Serializer angeben, der verwendet werden soll, wenn noch keine Datei erstellt wurde. - Verwenden Sie den von
dataStore
erstellten Property-Delegaten, um eine Instanz vonDataStore<T>
zu erstellen, wobeiT
der in der .proto-Datei definierte Typ ist. Rufen Sie es einmal auf der obersten Ebene Ihrer Kotlin-Datei auf und greifen Sie über diesen Eigenschaftsdelegat für den Rest der Anwendung darauf zu. Derfilename
-Parameter teilt DataStore mit, welche Datei zum Speichern der Daten verwendet werden soll, und derserializer
-Parameter teilt DataStore den Namen der in Schritt 1 definierten Serializer-Klasse mit.
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-Datenspeicher 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-Datenspeicher schreiben
Proto DataStore bietet eine updateData()
-Funktion, die ein gespeichertes Objekt transaktional aktualisiert. updateData()
gibt den aktuellen Status der Daten als Instanz Ihres Datentyps an und aktualisiert die Daten transaktional in einem atomaren Lese-Schreib-Ändern-Vorgang.
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 in einen asynchronen Code 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-Coroutinen bieten den runBlocking()
-Coroutinen-Builder, um die Lücke zwischen synchronem und asynchronem Code zu schließen. Sie können runBlocking()
verwenden, um Daten synchron aus DataStore zu lesen.
RxJava bietet Blockierungsmethoden 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();
Das Ausführen synchroner E/A-Vorgänge für den UI-Thread kann zu ANR-Fehlern oder UI-Verzögerungen führen. Sie können diese Probleme umgehen, indem Sie die Daten asynchron aus DataStore 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 einen Laufwerk-E/A-Vorgang ganz vermeiden, wenn der erste Lesevorgang abgeschlossen ist.
DataStore in Multi-Prozess-Code verwenden
Sie können DataStore so konfigurieren, dass über verschiedene Prozesse hinweg mit denselben Datenkonsistenzgarantien wie innerhalb eines einzelnen Prozesses auf dieselben Daten zugegriffen wird. Insbesondere garantiert DataStore:
- Bei Lesevorgängen werden nur Daten zurückgegeben, die auf dem Laufwerk gespeichert wurden.
- Lies-nach-Schreib-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 Datenspeicher 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 zwar erfassen und die Benutzeroberfläche aktualisieren,
val settings: Settings by dataStore.data.collectAsState() Text( text = "Last updated: $${settings.timestamp}", )
Damit Sie DataStore für verschiedene Prozesse 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
weist DataStore an, Ihren Datentyp zu lesen und zu schreiben.
Sie müssen einen Standardwert für den Serializer angeben, der verwendet werden soll, wenn noch keine Datei erstellt wurde. Im Folgenden sehen Sie eine Beispielimplementierung 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(...)
Feedback geben
Teilen Sie uns Ihr Feedback und Ihre Ideen über diese Ressourcen mit:
- Problemverfolgung
- Melden Sie Probleme, damit wir sie beheben können.
Weitere Informationen
Weitere Informationen zu Jetpack DataStore finden Sie in den folgenden zusätzlichen Ressourcen:
Produktproben
Blogs
Codelabs
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Auslagerungsdaten laden und anzeigen
- LiveData – Übersicht
- Layouts und Bindungsausdrücke