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:

  1. 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 Daten IllegalStateException aus.

  2. 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.

  3. Vermischen Sie SingleProcessDataStore und MultiProcessDataStore nie in derselben Datei. Wenn Sie über mehrere Prozesse auf DataStore zugreifen möchten, verwenden Sie immer MultiProcessDataStore.

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.0"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.0"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.0"
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation "androidx.datastore:datastore-preferences-core:1.1.0"
    }
    

Kotlin

    // Preferences DataStore (SharedPreferences like APIs)
    dependencies {
        implementation("androidx.datastore:datastore-preferences:1.1.0")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-preferences-rxjava2:1.1.0")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-preferences-rxjava3:1.1.0")
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-preferences-core:1.1.0")
    }
    

Proto-Datenspeicher

Groovig

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation "androidx.datastore:datastore:1.1.0"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-rxjava2:1.1.0"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-rxjava3:1.1.0"
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation "androidx.datastore:datastore-core:1.1.0"
    }
    

Kotlin

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation("androidx.datastore:datastore:1.1.0")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-rxjava2:1.1.0")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-rxjava3:1.1.0")
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-core:1.1.0")
    }
    

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:

  1. Definieren Sie eine Klasse, die Serializer<T> implementiert, wobei T 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.
  2. Verwenden Sie den von dataStore erstellten Property-Delegaten, um eine Instanz von DataStore<T> zu erstellen, wobei T 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. Der filename-Parameter teilt DataStore mit, welche Datei zum Speichern der Daten verwendet werden soll, und der serializer-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:

  1. 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)
              }
          }
    }
    
  2. 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