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:

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

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

  3. Verwende niemals SingleProcessDataStore und MultiProcessDataStore gleichzeitig für dieselbe Datei. Wenn Sie von mehr als einem Gerät aus auf das DataStore zugreifen möchten verwenden Sie immer MultiProcessDataStore.

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:

  1. Definieren Sie eine Klasse, die Serializer<T> implementiert, wobei T 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.
  2. Instanz mit dem von dataStore erstellten Attributdelegat erstellen von DataStore<T>, wobei T 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 Parameter filename wird angegeben, DataStore, in welcher Datei die Daten gespeichert werden sollen, und der Parameter serializer 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:

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