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:
- Defina uma
Serializer<T>
classe de implementação , queT
é o tipo definido no arquivo proto. Esta classe de serializador informa ao DataStore como ler e gravar seu tipo de dados. - Uma instância
Context.createDataStore()
criada usando uma função de extensãoDataStore<T>
, ondeT
é o tipo definido no arquivo proto.filename
O parâmetro informa ao DataStore qual arquivo usar para armazenar os dados e oserializer
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