Datenspeicher Teil von Android Jetpack
Jetpack DataStore ist eine Datenspeicherlösung, mit der Sie Schlüssel/Wert-Paare Paare oder typisierte Objekte mit protocol Puffer. DataStore verwendet Kotlin Koroutinen und Flow, um Daten asynchron, konsistent und transaktional.
Wenn Sie derzeit ein
SharedPreferences
bis
sollten Sie stattdessen eine Migration zu DataStore in Betracht ziehen.
Einstellungen-Datenspeicher und Proto DataStore
DataStore bietet zwei verschiedene Implementierungen: Preferences DataStore und Proto-Datenspeicher.
- Preferences DataStore speichert Daten mithilfe von Schlüsseln und greift darauf zu. Dieses -Implementierung erfordert kein vordefiniertes Schema und bietet Typsicherheit.
- Proto DataStore speichert Daten als Instanzen eines benutzerdefinierten Datentyps. Dieses -Implementierung erfordert, dass Sie ein Schema mithilfe von Protokollprotokollen zwischenspeichert, gibt aber Sicherheit.
DataStore richtig verwenden
Beachten Sie bei der korrekten Verwendung von DataStore immer die folgenden Regeln:
Erstellen Sie nie mehr als eine Instanz von
DataStore
für eine bestimmte Datei in um die gleichen Schritte durchzuführen. Dies kann dazu führen, dass alle DataStore-Funktionen beeinträchtigt werden. Wenn es für eine bestimmte Datei im selben Prozess aktiv sind,IllegalStateException
beim Lesen oder Aktualisieren von Daten ausgeben.Der generische Typ des Datenspeichers
muss unveränderlich sein. Typ ändern die in DataStore verwendet werden, entfällt jegliche Garantien, die DataStore bietet und erstellt potenziell schwerwiegenden, schwer zu fangenden Programmfehlern. Es wird dringend empfohlen, Protokollpuffer, die Unveränderlichkeitsgarantien, ein einfaches API und effizient Serialisierung.Verwende niemals
SingleProcessDataStore
undMultiProcessDataStore
gleichzeitig für dieselbe Datei. Wenn Sie von mehr als einem Gerät aus auf dasDataStore
zugreifen möchten verwenden Sie immerMultiProcessDataStore
.
Einrichten
Fügen Sie der Gradle-Datei Folgendes hinzu, um Jetpack DataStore in Ihrer App zu verwenden je nachdem, welche Implementierung Sie verwenden möchten:
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 mit „Preferences“-Datenspeicher speichern
Die Datenspeicher-Implementierung von „Preferences“ verwendet die
DataStore
und
Preferences
Klassen, um einfache Schlüssel/Wert-Paare auf dem Laufwerk zu speichern.
Voreinstellungen-Datenspeicher 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 während des restlichen Anwendungsbereichs über diese Eigenschaft darauf zu. So kannst du DataStore
leichter als Singleton behalten. Alternativ können Sie auch RxPreferenceDataStoreBuilder
verwenden
wenn Sie RxJava verwenden. Der obligatorische name
-Parameter ist der Name des
Einstellungen-Datenspeicher.
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 der Datastore für Einstellungen kein vordefiniertes Schema verwendet, müssen Sie die Methode
Schlüsseltypfunktion zum Definieren eines Schlüssels für jeden Wert, den Sie benötigen,
in der Instanz DataStore<Preferences>
speichern. Um z. B. einen Schlüssel zu definieren,
Verwenden Sie für einen Ganzzahlwert
intPreferencesKey()
Verwenden Sie dann die Methode
Property DataStore.data
um den entsprechenden gespeicherten Wert mithilfe einer Flow
bereitzustellen.
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“-Datenspeicher schreiben
„Preferences DataStore“ bietet eine
edit()
, die die Daten in einer DataStore
transaktional aktualisiert. Der Wert der Funktion
Der Parameter transform
akzeptiert einen Codeblock, in dem Sie die Werte aktualisieren können:
erforderlich. Der gesamte Code im Transformationsblock wird als
Transaktion.
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.
Typierte Objekte mit Proto DataStore speichern
Die Proto DataStore-Implementierung verwendet DataStore und Protokoll Zwischenspeichern, um die auf dem Laufwerk gespeichert.
Schema definieren
Proto DataStore erfordert ein vordefiniertes Schema in einer .proto-Datei im
app/src/main/proto/
-Verzeichnis. Dieses Schema definiert den Typ der Objekte
die in Ihrem Proto DataStore beibehalten werden. Weitere Informationen zum Definieren eines Proto
-Schema ist unter der Sprache 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 sind zwei Schritte erforderlich, um Ihre eingegebenen Objekte:
- Definieren Sie eine Klasse, die
Serializer<T>
implementiert, wobeiT
der definierte Typ ist in der .proto-Datei. Diese Serializer-Klasse teilt DataStore mit, wie Ihren Datentyp. Sie müssen einen Standardwert für den Serializer angeben, wird verwendet, wenn noch keine Datei erstellt wurde. - Instanz mit dem von
dataStore
erstellten Attributdelegat erstellen vonDataStore<T>
, wobeiT
der in der .proto-Datei definierte Typ ist. Anruf einmal auf der obersten Ebene Ihrer Kotlin-Datei und greifen über diese Eigenschaft darauf zu. für den Rest der App delegieren. Mit dem Parameterfilename
wird angegeben, DataStore, in welcher Datei die Daten gespeichert werden sollen, und der Parameterserializer
teilt DataStore den Namen der Serializer-Klasse mit wie in Schritt 1 definiert.
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. Sie erhalten von updateData()
den aktuellen Status der Daten als Instanz Ihres Datentyps
in einem atomaren Lese-Schreib-Änderungs-Vorgang transaktional ausführen.
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, die aber möglicherweise nicht können Sie den umgebenden Code immer asynchron ändern. Dieses wenn Sie mit einer vorhandenen Codebasis arbeiten, synchrone Laufwerk-E/A oder wenn Sie eine Abhängigkeit haben, die kein asynchrone API verwenden.
Kotlin-Koroutinen bieten die
runBlocking()
Koroutinen-Builder, um die Lücke zwischen synchronem und asynchronem Zugriff zu schließen
Code. Mit runBlocking()
können Sie Daten synchron aus DataStore lesen.
RxJava bietet Blockierungsmethoden für Flowable
. Der folgende Code blockiert den Aufruf
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 im UI-Thread kann zu ANR-Fehler oder Verzögerungen in der Benutzeroberfläche. Sie können diese Probleme beheben, indem Sie die Daten aus DataStore:
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äter
synchrone Lesevorgänge mit runBlocking()
sind möglicherweise schneller oder vermeiden Laufwerk-E/A-Vorgänge
wenn der erste Lesevorgang abgeschlossen ist.
DataStore in Multi-Prozess-Code verwenden
Sie können DataStore so konfigurieren, dass in verschiedenen Prozessen auf dieselben Daten zugegriffen wird. mit denselben Datenkonsistenzgarantien wie innerhalb eines einzelnen Prozesses. In DataStore garantiert Folgendes:
- Bei Lesevorgängen werden nur die Daten zurückgegeben, die auf dem Laufwerk gespeichert wurden.
- Konsistenz zwischen Lese- und Schreibvorgängen.
- Schreibvorgänge sind serialisiert.
- Lesevorgänge werden niemals 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 regelmäßig den Datenspeicher
<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 ihre Benutzeroberfläche aktualisiert
val settings: Settings by dataStore.data.collectAsState() Text( text = "Last updated: $${settings.timestamp}", )
Um DataStore in verschiedenen Prozessen verwenden zu können, müssen Sie
das DataStore-Objekt mithilfe von MultiProcessDataStoreFactory
.
val dataStore: DataStore<Settings> = MultiProcessDataStoreFactory.create(
serializer = SettingsSerializer(),
produceFile = {
File("${context.cacheDir.path}/myapp.preferences_pb")
}
)
serializer
teilt DataStore mit, wie der Datentyp gelesen und geschrieben werden soll.
Stellen Sie sicher, dass Sie einen Standardwert für den Serializer angeben, der verwendet werden soll, falls
noch keine Datei erstellt. Unten 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()
)
}
}
Sie können die Hilt-Abhängigkeit verwenden. einschleusen, um sicherzustellen, dass Ihre DataStore-Instanz pro Prozess eindeutig ist:
@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
MultiProcessDataStoreFactory.create(...)
Feedback geben
Wir freuen uns über dein Feedback und deine Ideen:
- Problemverfolgung
- Melde 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.
- Daten aus Seiten laden und anzeigen
- LiveData-Übersicht
- Layouts und Bindungsausdrücke