当前位置:   article > 正文

Android Jetpack组件DataStore之Proto与Preferences存储详解与使用_android proto

android proto

一、介绍

        Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。

如果您当前在使用 SharedPreferences 存储数据,请考虑迁移到 DataStore,Datastore在存储安全和性能都是有保障的。

二、库的介绍与使用

Preferences DataStore 和 Proto DataStore

DataStore 提供两种不同的实现:Preferences DataStore 和 Proto DataStore。

  • Preferences DataStore 使用键存储和访问数据。此实现不需要预定义的架构,也不确保类型安全。
  • Proto DataStore 将数据作为自定义数据类型的实例进行存储。此实现要求您使用协议缓冲区来定义架构,但可以确保类型安全。

为了正确使用 DataStore,请始终谨记以下规则:

  1. 请勿在同一进程中为给定文件创建多个 DataStore 实例,否则会破坏所有 DataStore 功能。如果给定文件在同一进程中有多个有效的 DataStore,DataStore 在读取或更新数据时将抛出 IllegalStateException

  2. DataStore 的通用类型必须不可变。更改 DataStore 中使用的类型会导致 DataStore 提供的所有保证失效,并且可能会造成严重的、难以发现的 bug。强烈建议您使用可保证不可变性、具有简单的 API 且能够高效进行序列化的协议缓冲区。

  3. 切勿在同一个文件中混用 SingleProcessDataStore 和 MultiProcessDataStore。如果您打算从多个进程访问 DataStore,请始终使用 MultiProcessDataStore

依赖库接入

implementation("androidx.datastore:datastore-preferences:1.0.0") implementation("androidx.datastore:datastore-preferences-core:1.0.0") implementation("androidx.datastore:datastore:1.0.0") implementation("androidx.datastore:datastore-core:1.0.0")

 Preferences DataStore 存储

          使用由 preferencesDataStore 创建的属性委托来创建 Datastore<Preferences> 实例。在您的 Kotlin 文件顶层调用该实例一次,便可在应用的所有其余部分通过此属性访问该实例。这样可以更轻松地将 DataStore 保留为单例。此外,如果您使用的是 RxJava,请使用 RxPreferenceDataStoreBuilder。必需的 name 参数是 Preferences DataStore 的名称

注意:以下代码以kotlin为开发语言

接入流程:

1、初始化DataStore<Preferences>

val Context.dataStore: DataStore<Preferences> by preferencesDataStore("my_datastore")

先定义一个扩展函数,如果不了解扩展函数,可以查看关于kotlin的详解

Kotlin语法详解与实践教程,区分JAVA以及如何闭坑_蜗牛、Z的博客-CSDN博客_kotline

Android kotlin在实战过程问题总结与开发技巧详解_蜗牛、Z的博客-CSDN博客_android kotlin 实战

这样持有全局变量,在其他地方只要传入context就可以拥有datastore的对象,preferences是通过preferencesDataStore存储的。这个会和接下来讲解Proto的存储区分开来

2、保存数据

datastore是通过事物提交,context.dataStore.edit;

edit 源码

 所以我们要在提交的时候把map要保持的数据给封装好

  1. val edit = context.dataStore.edit { map ->
  2. map[keyValue] = (value as T)
  3. }

关于key:

        这里的key不是我们正常的string或者int,需要通过转换一下。

转换的类PreferencesKey已提供了以下key支持:

  1. @JvmName("intKey")
  2. public fun intPreferencesKey(name: String): Preferences.Key<Int> = Preferences.Key(name)
  3. @JvmName("doubleKey")
  4. public fun doublePreferencesKey(name: String): Preferences.Key<Double> = Preferences.Key(name)
  5. @JvmName("stringKey")
  6. public fun stringPreferencesKey(name: String): Preferences.Key<String> = Preferences.Key(name)
  7. @JvmName("booleanKey")
  8. public fun booleanPreferencesKey(name: String): Preferences.Key<Boolean> = Preferences.Key(name)
  9. @JvmName("floatKey")
  10. public fun floatPreferencesKey(name: String): Preferences.Key<Float> = Preferences.Key(name)
  11. @JvmName("longKey")
  12. public fun longPreferencesKey(name: String): Preferences.Key<Long> = Preferences.Key(name)
  13. @JvmName("stringSetKey")
  14. public fun stringSetPreferencesKey(name: String): Preferences.Key<Set<String>> =
  15. Preferences.Key(name)

key和value的类型要对应,否则在在编译的时候会报错,直接点,value是什么值,key在创建的时候就申请说明类型。

如:

value="zhangsan",那么key就是stringPreferencesKey("key")。

否则报错:

 因为源码在设计的时候,已限定好了:

源码

保存数据

  1. suspend fun <T> put(context: Context, key: String, value: Any, type: T) {
  2. val keyValue = getKey(key, type)
  3. val edit = context.dataStore.edit { map ->
  4. map[keyValue] = value as T
  5. }
  6. }

获取数据

获取数据通过map来获取到flow,通过flow进行流转。

  1. suspend fun <T> getValue(context: Context, key: String, type: T): Flow<Any?> {
  2. val keyValue = getKey(key, type)
  3. val flow = context.dataStore.data.map { map ->
  4. map[keyValue]
  5. }
  6. return flow
  7. }

如何创建Key?

第一种:为你的每种类型都加一个put个get类型

第二种:如果你觉得每种写法代码臃肿,可以通过泛型来匹配

  1. private fun <T> getKey(key: String, type: T): Preferences.Key<T> {
  2. var keyValue: Preferences.Key<T>? = null
  3. val TypeValue = type.toString()
  4. if (TypeValue.endsWith(Int::class.java.name)) {
  5. keyValue = intPreferencesKey(key) as Preferences.Key<T>
  6. } else if (TypeValue.endsWith(String::class.java.name)) {
  7. keyValue = stringPreferencesKey(key) as Preferences.Key<T>
  8. } else if (TypeValue.endsWith(Double::class.java.name)) {
  9. keyValue = doublePreferencesKey(key) as Preferences.Key<T>
  10. } else if (TypeValue.endsWith(Float::class.java.name)) {
  11. keyValue = floatPreferencesKey(key) as Preferences.Key<T>
  12. } else if (TypeValue.endsWith(Boolean::class.java.name)) {
  13. keyValue = booleanPreferencesKey(key) as Preferences.Key<T>
  14. } else if (TypeValue.endsWith(Long::class.java.name)) {
  15. keyValue = longPreferencesKey(key) as Preferences.Key<T>
  16. } else if (TypeValue.endsWith(Set::class.java.name)) {
  17. keyValue = stringSetPreferencesKey(key) as Preferences.Key<T>
  18. } else {
  19. throw IllegalAccessException("key type is not support,you need check you key type!!")
  20. }
  21. return keyValue
  22. }

如何调用?

DataStoreConfig.put(
    context!!,
    "name",
    bind.editSave.text.toString(),
    String::class.java
)

bind.editSave.text.toString()是edittext的输入文本。可以直接调用上方的方法

注意:suspend

suspend叫协程,调用类时,需要所调用的地方也要是suspend。

如何处理?

第一:同步

Kotlin 协程提供 runBlocking() 协程构建器,以帮助消除同步与异步代码之间的差异。您可以使用 runBlocking() 从 DataStore 同步读取数据

  1. runBlocking {
  2. DataStoreConfig.put(
  3. context!!,
  4. "name",
  5. bind.editSave.text.toString(),
  6. String::class.java
  7. )
  8. }

第二中:异步

异步这边介绍一个框架,kotlinx-coroutines-core

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"

一样,定义一个扩展函数

  1. val Context.scope: CoroutineScope
  2. get() = CoroutineScope(EmptyCoroutineContext)
  1. context!!.scope.launch {
  2. DataStoreConfig.put(
  3. context!!,
  4. "name",
  5. bind.editSave.text.toString(),
  6. String::class.java
  7. )
  8. }

扩展函数

        kotlin支持对类的函数进行扩展,类似动态新增方法。和定义方法一样,在任何类里面都可以对任何类进行扩展。

扩展写法:类名.扩展对象,方法也是一样

Proto DataStore存储

        Proto DataStore是对象存储,proto并非Java或者kotlin的语言,需要通过第三方插件生成java或者kotlin的对象。该方法和AIDL对象一样。这边先介绍proto对象的使用,如何接入,请看

         Proto 对象促存储和preferences是 DataStore的两种存储方式,一个是对象存储,一个是键值对key-value的存储。

创建 Proto DataStore 来存储类型化对象涉及两个步骤

  1. 定义一个实现 Serializer<T> 的类,其中 T 是 proto 文件中定义的类型。此序列化器类会告知 DataStore 如何读取和写入您的数据类型。请务必为该序列化器添加默认值,以便在尚未创建任何文件时使用。
  2. 使用由 dataStore 创建的属性委托来创建 DataStore<T> 的实例,其中 T 是在 proto 文件中定义的类型。在您的 Kotlin 文件顶层调用该实例一次,便可在应用的所有其余部分通过此属性委托访问该实例。filename 参数会告知 DataStore 使用哪个文件存储数据,而 serializer 参数会告知 DataStore 第 1 步中定义的序列化器类的名称。

Proto文件的创建

先在main文件夹下创建一个proto文件夹,再在proto文件夹新建一个后缀proto的文件

  1. syntax = "proto3";
  2. option java_package = "com.example.wiik.testdemo.proto";
  3. option java_multiple_files = true;
  4. message Settings {
  5. int32 example_counter = 1;
  6. string name=2;
  7. }

这里定义两个变量example_counter 和name,然后rebuild项目,生成对象

 

GeneratedMessageV3是proto的语言版本,这样就完成了一个proto的文件生成。

代码接入

1、创建继承Serializer的类。

  1. object SettingsSerializer : Serializer<Settings> {
  2. override val defaultValue: Settings
  3. get() = Settings.getDefaultInstance()
  4. override suspend fun readFrom(input: InputStream): Settings {
  5. return Settings.parseFrom(input)
  6. }
  7. override suspend fun writeTo(t: Settings, output: OutputStream) {
  8. t.writeTo(output)
  9. }
  10. }

2、创建dataStore对象

  1. val Context.proDataStore: DataStore<Settings> by dataStore(
  2. fileName = "settings.pb",
  3. serializer = SettingsSerializer
  4. )

这里依旧是采用扩展,在SettingsSerializer 顶部扩展,需要的可以看:

Android DataStore Proto存储接入流程详解与使用_蜗牛、Z的博客-CSDN博客

存储对象

这里的存储是通过datastore的updatedata来完成

  1. suspend fun put(context: Context, name: String, count: Int = 100) {
  2. context.proDataStore.updateData { store ->
  3. store.toBuilder().setName(name).setExampleCounter(count).build()
  4. }
  5. }

获取对象

proto的对象获取与preferences是一样的,都是获取到flow,通过flow来流传

  1. suspend fun getSetting(context: Context): Flow<Settings> {
  2. val flow = context.proDataStore.data.map {
  3. it
  4. }
  5. return flow
  6. }

  1. scope.launch {
  2. val set = SettingsSerializer.getSetting(context!!)
  3. set?.let {
  4. set.collect { set ->
  5. runOnUiThread {
  6. bind.textResultProto.text =
  7. "name=${set?.name},age=${set?.exampleCounter}"
  8. }
  9. }
  10. }
  11. }

这些都是在子线程操作,如果需要更新,需要通过UI线程来完成。

SharePreferences转移到Preference存储

        preferences的出现就是要取代SharePreferences,并且已兼容,如果SharePreferences的存储无法转换到preferences,也就意味着SharePreferences数据将会丢失。

在转移这边,datastore同样也支持。

  1. public fun preferencesDataStore(
  2. name: String,
  3. corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
  4. produceMigrations: (Context) -> List<DataMigration<Preferences>> = { listOf() },
  5. scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
  6. )

        在创建preferencesDataStore的时候,produceMigrations就是需要转移的sharepreference对象,

只要在初始化的时候加进去即可。

同步

  1. val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
  2. "my_datastore",
  3. produceMigrations = { it ->
  4. listOf(SharedPreferencesMigration(it, "sp_test"))
  5. })

我们只需要加入我们的sp的name即可,支持合并多个,如果你当前工程下有多个sp,就创建多少个加入到listof()即可。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/309334
推荐阅读
相关标签
  

闽ICP备14008679号