Use Jetpack DataStore para armazenamento de dados

Jetpack DataStore é uma solução de armazenamento de dados que permite armazenar pares de valores-chave ou objetos digitados usando buffers de protocolo . DataStore usa corrotinas e processos Kotlin para armazenar dados em transações assíncronas e consistentes.

Se você atualmente usa  SharedPreferences dados armazenados, considere migrar para DataStore.

Observação : se você precisar oferecer suporte a conjuntos de dados grandes ou complexos, atualizações parciais ou integridade referencial, considere usar o  Room em vez do DataStore. DataStore é muito adequado para pequenos conjuntos de dados simples e não suporta atualizações parciais ou integridade referencial.

Preferências DataStore 和 Proto DataStore

DataStore fornece duas implementações diferentes: Preferências DataStore e Proto DataStore.

  • Preferências DataStore  usa chaves para armazenar e acessar dados. Esta implementação não requer uma arquitetura predefinida, nem garante a segurança de tipo.
  • O Proto DataStore  armazena dados como instâncias de tipos de dados personalizados. Essa implementação requer que você use buffers de protocolo para definir a arquitetura, mas pode garantir a segurança do tipo.

 

Bem-vindo ao Jetpack DataStore, esta é uma solução de armazenamento de dados nova e aprimorada projetada para substituir as Preferências Compartilhadas originais. Jetpack DataStore é desenvolvido com base na corrotina e fluxo Kotlin e oferece duas implementações diferentes:  Proto DataStore  e  Preferences DataStore . Entre eles,  Proto DataStore , pode armazenar objetos com tipos (implementados usando buffers de protocolo); Preferences DataStore , pode armazenar pares de chave-valor . No DataStore, os dados são armazenados de maneira assíncrona, consistente e transacional, que supera a maioria das deficiências de SharedPreferences.

 

  • buffers de protocolo

    https://developers.google.cn/protocol-buffers

Comparação de SharedPreferences e DataStore

* SharedPreferences tem uma API síncrona que parece ser segura para chamar a partir do thread da interface do usuário, mas a API realmente executa operações de E / S de disco. Além disso, o método apply () bloqueará o thread de IU em fsync (). Em qualquer lugar em seu aplicativo, sempre que um serviço ou atividade é iniciado ou interrompido, ele aciona uma chamada para esperar por fsync (). O processo de chamada fsync () organizado por apply () bloqueará o thread de IU, que geralmente se torna a fonte de ANR.

 

** SharedPreferences lançará exceções de tempo de execução ao analisar erros.

 

  • ANR

    https://developer.android.google.cn/topic/performance/vitals/anr

Em ambas as implementações, a menos que especificado de outra forma, o DataStore armazenará preferências em arquivos e todas as operações de dados serão realizadas em Dispatchers.IO.

 

Embora o DataStore de Preferências e o Proto DataStore possam armazenar dados, seus métodos de implementação são diferentes:

  • O Preference DataStore , como o SharedPreferences, não pode definir o esquema ou garantir o acesso aos valores-chave com o tipo correto.

  • O Proto DataStore  permite definir esquemas usando buffers de protocolo. Use Protobufs para preservar dados fortemente tipados . Eles são mais rápidos, menores e menos ambíguos do que XML ou outros formatos de dados semelhantes. Embora o Proto DataStore exija que você aprenda um novo mecanismo de serialização, considerando as vantagens do esquema fortemente tipado trazido pelo Proto DataStore, acreditamos que o preço vale a pena.

  • Buffers de protocolo

    https://developers.google.cn/protocol-buffers

Comparação de Room e DataStore

Se você precisar atualizar dados localmente, integridade referencial ou suportar conjuntos de dados grandes e complexos, você deve considerar o uso de  Room em  vez de DataStore. O DataStore é ideal para conjuntos de dados pequenos e simples, mas não oferece suporte a atualizações parciais e integridade referencial.

 

 

Um, use DataStore

Se desejar usar o Jetpack DataStore em seu aplicativo, adicione o seguinte ao arquivo Gradle de acordo com a implementação que deseja usar: Primeiro, adicione a dependência DataStore. Se você estiver usando o Proto DataStore, certifique-se de adicionar também a dependência proto:

  • proto dependências

    https://github.com/google/protobuf-gradle-plugin

def dataStoreVersion = "1.0.0-alpha05"

// 在 Android 开发者网站上确认最新的版本号

// https://developer.android.google.cn/jetpack/androidx/releases/datastore

 

// Preferences DataStore

implementation "androidx.datastore:datastore-preferences:$dataStoreVersion"

// Preferências DataStore (SharedPreferences like APIs) 
dependencies { 
  deployment "androidx.datastore: datastore-preferences: 1.0.0-alpha05" 
} 
// Alternativamente - use o seguinte artefato sem uma dependência Android. 
dependências { 
  implementação "androidx.datastore: datastore-preferences-core: 1.0.0-alpha05" 
}

 

 

// Proto DataStore   

implementation  "androidx.datastore:datastore-core:$dataStoreVersion"

// Dependências de DataStore digitadas (superfície de API digitada, como Proto) 
{ 
  implementação "androidx.datastore: datastore: 1.0.0-alpha05" 
} 
// Alternativamente - use o seguinte artefato sem uma dependência Android. 
dependências { 
  implementação "androidx.datastore: datastore-core: 1.0.0-alpha05" 
}

 

Dois, crie um DataStore

Você pode usar o método de extensão Context.createDataStore () para criar um DataStore:  

Use Preferences DataStore para armazenar pares de valores-chave

A implementação do Preferences DataStore usa  DataStore e  Preferences classes para manter pares de valores-chave simples no disco.

Criar DataStore de Preferências

 Uma instância Context.createDataStore() criada usando uma  função de extensão  DataStore<Preferences>. O name parâmetro necessário  é o nome do DataStore de Preferências.

// 创建 Preferences DataStore

val dataStore: DataStore<Preferences> = context.createDataStore(

   name = "settings"

)

 

Use Proto DataStore para armazenar objetos digitados

A implementação do Proto DataStore usa DataStore e buffers de protocolo para manter os objetos digitados no disco.

Defina a arquitetura

O Proto DataStore requer que a app/src/main/proto/ arquitetura predefinida seja salva no  arquivo proto do diretório. Este esquema é usado para definir o tipo de objetos que você salva no Proto DataStore. Para obter detalhes sobre como definir a arquitetura proto, consulte o  guia de linguagem protobuf .

Ao usar o Proto DataStore, você precisa usar o arquivo proto no diretório app / src / main / proto / para definir seu próprio esquema. Para obter mais informações sobre como definir o proto schema, consulte o guia de linguagem protobuf.

  • guia de linguagem protobuf

    https://developers.google.cn/protocol-buffers/docs/proto3

syntax = "proto3";

option java_package = "<your package name here>";

option java_multiple_files = true;

message Settings {

  int my_counter = 1;

}

Nota : A classe de seu objeto de armazenamento é message gerada pela definição no arquivo proto em tempo de compilação  . Certifique-se de reconstruir seu projeto.

Criar Proto DataStore

Se você estiver usando o Proto DataStore, também precisará implementar a interface Serializer para informar ao DataStore como ler e gravar seu tipo de dados.

A criação de um Proto DataStore para armazenar objetos digitados envolve duas etapas:

  1. Defina uma Serializer<T> classe de implementação  , que  T é o tipo definido no arquivo proto. Esta classe de serializador informa ao DataStore como ler e gravar seu tipo de dados.
  2.  Uma instância Context.createDataStore() criada usando uma  função de extensão  DataStore<T>, onde  T é o tipo definido no arquivo proto. filename O parâmetro informa ao DataStore qual arquivo usar para armazenar os dados e o  serializer parâmetro informa o nome da classe do serializador definida na Etapa 1 do DataStore.

object SettingsSerializer : Serializer<Settings> {

   override fun readFrom(input: InputStream): Settings {

     try {

        return Settings.parseFrom(input)

      } catch (exception: InvalidProtocolBufferException) {

       throw CorruptionException("Cannot read proto.", exception)

      }

   }

   override fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output)

}

// 创建 Proto DataStore

val settingsDataStore: DataStore<Settings> = context.createDataStore(

   fileName = "settings.pb",

   serializer = SettingsSerializer

)

 

 

Três, leia os dados do DataStore

Quer seja um objeto Preferences ou um objeto definido no proto schema, o DataStore irá expor os dados armazenados na forma de Flow. O DataStore pode garantir que os dados sejam recuperados em Dispatchers.IO, portanto, não bloqueará seu thread de UI.

  • Dispatchers.IO

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-io.html

 

Leia o conteúdo do DataStore de Preferências

Como o Preferences DataStore não usa um esquema predefinido, você deve usar  preferencesKey() para DataStore<Preferences> definir uma chave para cada valor que precisa ser armazenado na  instância. Em seguida, use o  DataStore.data atributo,  Flow fornecendo o valor armazenado apropriado.

// 使用 Preferências DataStore:

val MY_COUNTER = preferencesKey<Int>("my_counter")

val myCounterFlow: Flow<Int> = dataStore.data

.map { currentPreferences ->

   // 不同于 Proto DataStore,这里不保证类型安全。

   currentPreferences[MY_COUNTER] ?: 0

}

 

Leia o conteúdo do Proto DataStore

Use para  DataStore.data exibir as propriedades correspondentes do objeto armazenado  Flow.

// Use Proto DataStore:

val myCounterFlow: Flow<Int> = settingsDataStore.data

.map { settings ->

   // myCounter 属性由您的 proto schema 生成!

   settings.myCounter

}

 

Quarto, gravar dados no DataStore 

Para gravar dados, DataStore fornece uma função de suspensão DataStore.updateData (), que fornecerá a você o status dos dados armazenados atuais como um parâmetro. Isso é verdadeiro para o objeto Preferences ou a instância do objeto que você definiu no esquema proto . A função updateData () usa operações atômicas de leitura, gravação e modificação e atualiza dados de maneira transacional. Quando os dados forem armazenados no disco, a co-rotina será concluída.

Preferências DataStore também fornece uma função DataStore.edit () para facilitar a atualização de dados. Nesta função, você receberá um objeto MutablePreferences para edição em vez de um objeto Preferences. Esta função, como updateData (), aplicará a modificação ao disco após a conversão do bloco de código ser concluída, e a co-rotina será concluída quando os dados forem armazenados no disco.

 

Grave o conteúdo no DataStore de Preferências

Preferências DataStore fornece uma  edit() função para atualizar DataStore os dados de uma maneira transacional  . Os transform parâmetros desta função  aceitam blocos de código, onde você pode atualizar os valores conforme necessário. Todo o código no bloco de conversão é tratado como uma única transação.

// 使用 Preferências DataStore:

suspend fun incrementCounter() {

  dataStore.edit { settings ->

    // 可以安全地增加我们的计数器,而不会因为资源竞争而丢失数据。

    val currentCounterValue = settings[MY_COUNTER] ?: 0

    settings[MY_COUNTER] = currentCounterValue + 1

  }

}

 

Grave conteúdo no Proto DataStore

O Proto DataStore fornece uma  updateData() função para atualizar objetos armazenados de uma maneira transacional. updateData() Fornece o estado atual dos dados como uma instância do tipo de dados e atualiza os dados de uma maneira transacional em uma operação atômica de leitura-gravação-modificação.

// Use Proto DataStore:

suspend fun incrementCounter() {

  settingsDataStore.updateData { currentSettings ->

    // 可以安全地增加我们的计数器,而不会因为资源竞争而丢失数据。

    currentSettings.toBuilder()

     .setMyCounter(currentSettings.myCounter + 1)

     .build()

  }

}

 

Quinto, use DataStore no código de sincronização

Nota : Por favor, tente evitar o bloqueio do thread ao ler os dados do DataStore. Bloquear threads de interface pode causar  ANR  ou congelamentos de interface, enquanto o bloqueio de outras threads pode causar deadlocks .

Uma das principais vantagens do DataStore é a API assíncrona, mas nem sempre é possível alterar o código circundante para código assíncrono. Isso pode acontecer se você estiver usando uma base de código existente que usa E / S de disco síncrona ou se suas dependências não fornecerem APIs assíncronas.

As corrotinas Kotlin fornecem  runBlocking() construtores de corrotinas para ajudar a eliminar a diferença entre o código síncrono e assíncrono. Você pode usar para  runBlocking() ler dados de forma síncrona do DataStore. O código a seguir bloqueará o thread de chamada até que o DataStore retorne os dados:

val exampleData = runBlocking { dataStore.data.first() }

Executar operações de E / S síncronas em threads de interface pode causar ANR ou congelamentos de interface. Você pode aliviar esses problemas pré-carregando dados de forma assíncrona do DataStore:

override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        dataStore.data.first()
        // You should also handle IOExceptions here.
    }
}

Dessa forma, o DataStore pode ler dados de forma assíncrona e armazená-los em cache na memória. runBlocking() Pode ser mais rápido usar a leitura síncrona no futuro  ou, se a leitura inicial tiver sido concluída, também pode ser possível evitar completamente as operações de E / S de disco.

 

 

6. Migrar de SharedPreferences para DataStore 

Para migrar de SharedPreferences para DataStore, você precisa passar o objeto SharedPreferencesMigration para o construtor DataStore, e DataStore pode concluir automaticamente a migração de SharedPreferences para DataStore. A migração será executada antes de ocorrer qualquer acesso a dados no DataStore, o que significa que sua migração deve ter sido bem-sucedida antes que DataStore.data retorne qualquer valor e DataStore.updateData () possa atualizar os dados.

 

Se desejar migrar para o DataStore de Preferências, você pode usar a implementação padrão de SharedPreferencesMigration. Você só precisa passar o nome usado na construção de SharedPreferences.

使用 Preferências DataStore:

val dataStore: DataStore<Preferences> = context.createDataStore(

  name = "settings",

  migrations = listOf(SharedPreferencesMigration(context, "settings_preferences"))

)

Quando você precisa migrar para Proto DataStore, deve implementar uma função de mapeamento para definir como migrar os pares de chave-valor usados ​​por SharedPreferences para seu esquema DataStore definido.

Use Proto DataStore:

val settingsDataStore: DataStore<Settings> = context.createDataStore(

  produceFile = { File(context.filesDir, "settings.preferences_pb") },

  serializer = SettingsSerializer,

  migrations = listOf(

    SharedPreferencesMigration(

      context,

      "settings_preferences"

    ) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->

      // 在这里将 sharedPrefs 映射至您的类型。

    }

  )

)

 

Sete, resumo

SharedPreferences tem muitas falhas: parece que a API síncrona que pode ser chamada com segurança a partir do thread de IU na verdade não é segura, não há mecanismo para solicitar erros, a falta de APIs de transação etc. DataStore é uma alternativa às preferências compartilhadas, ele resolve a maioria dos problemas de preferências compartilhadas. DataStore contém APIs totalmente assíncronas implementadas usando corrotinas Kotlin e Flow, que podem lidar com a migração de dados, garantir a consistência dos dados e lidar com a corrupção de dados.

 

  • Documentação

    https://developer.android.google.cn/datastore

  • Codelab de DataStore de Preferências

    https://developer.android.google.cn/codelabs/android-preferences-datastore#0

  • Codelab Proto DataStore

    https://developer.android.google.cn/codelabs/android-proto-datastore#0

  • Rastreador de problemas

    https://issuetracker.google.com/issues/new?component=907884&template=1466542

 

 

 

 

 

Acho que você gosta

Origin blog.csdn.net/MYBOYER/article/details/112983293
Recomendado
Clasificación