当前位置:   article > 正文

Jetpack的DataStore数据存储组件的使用简介_mutablepreferences 存储失败

mutablepreferences 存储失败

Jetpack的DataStore数据存储组件的使用简介

介绍

Jetpack DataStore是一种数据存储解决方案,可让您使用Protocol Buffer(协议缓冲区)存储键值对或类型化对象。DataStore使用Kotlin协程Flow异步,一致的事务方式来存储数据。

注意: 如果需要支持大型或复杂的数据集,部分更新或参考完整性,请考虑使用 Room 而不是DataStore。DataStore非常适合小型,简单的数据集,并且不支持部分更新或参照完整性。

目的

Jetpack DataStore 是经过改进的新版数据存储解决方案,旨在取代 SharedPreferences。以异步、一致的事务方式存储数据,克服了 SharedPreferences 的大部分缺点。

DataStore实现方式

DataStore 基于 Kotlin 协程和流程构建而成,提供两种不同的实现,分别如下:

  • Preferences DataStore方式

使用键存储和访问数据。此实现不需要预定义的架构,并且不提供类型安全性。

  • Proto DataStore方式

将数据存储为自定义数据类型的实例。此实现要求您使用协议缓冲区定义架构,但它提供类型安全性。

SharedPreferences 与 DataStore 支持功能的对比

特征SharedPreferencesPreferencesDataStoreProtoDataStore
是否支持异步
是否支持同步
是否支持在UI线程调用
可以发出错误信号
避免运行时异常
类型安全
一致的事务方式存储数据

.

.

DataStore 的简单使用

DataStore的使用就是: Preferences DataStore 的使用 和 Proto DataStore 的使用

PreferencesDataStore 的使用

build.gradle 文件添加 DataStore 的依赖

dependencies {
  // Preferences DataStore
  implementation "androidx.datastore:datastore-preferences:1.0.0-alpha04"
}
  • 1
  • 2
  • 3
  • 4

.

PreferencesDataStore 的使用步骤如下:

1. 创建Preferences DataStore

//定义 DataStore 的名字
private val MY_DATA_STORE_NAME = "DataStorePreference"

//创建DataStore对象
val dataStore: DataStore<Preferences> = context.createDataStore(
  name = DATASTORE_PREFERENCE_NAME
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注:
name属性必须要设置,不然无法使用 DataStore

2. 插入数据

private suspend fun saveData(value: String) {
		//创建key
        var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)
		//插入数据
        dataStore.edit { mutablePreferences ->
            mutablePreferences[preKey] = value
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

PreferencesDataStore 中是通过 DataStore.edit() 写入数据,edit 方法是个 suspend 函数,必须在协程中进行调用;

3. 根据key来读取数据

private suspend fun readDara(): String {
		//创建key
        var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)
		
		//根据key读取数据
        var value = dataStore.data.map { preferences ->
            preferences[preKey] ?: ""
        }
        return value.first()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4. 读取数据时处理异常时

private suspend fun readData(): String {
		//创建key
        var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)

		//根据key读取数据
        var value = dataStore.data
	    .catch { exception ->
			//进行异常处理
	        if (exception is IOException) {
	           //异常处理
	        } else {
				//抛出异常
	            throw exception
	        }
    	}.map { preferences ->
            preferences[preKey] ?: ""
        }
        return value.first()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

补充:

关于Key的说明:

创建Key: var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)

key 的类型是 Preferences.Key,但是只支持 Int , String, Boolean , Float , Long 类型

从 SharedPreference 迁移数据

为了能够将其迁移到 DataStore,我们需要更新dataStore构建器以将传递SharedPreferencesMigration给迁移列表。

注:
DataStore将能够自动从SharedPreferences迁移到DataStore。必须先运行迁移,然后才能在DataStore中进行任何数据访问。这意味着在DataStore.data发出任何值和DataStore.edit()更新数据之前,您的迁移必须已经成功。

迁移代码:

//定义 DataStore 的名字
private val MY_DATA_STORE_NAME = "DataStorePreference"

val dataStore: DataStore<Preferences> = this.createDataStore(
    name = MY_DATA_STORE_NAME,
    migrations = listOf(SharedPreferencesMigration(this, USER_PREFERENCES_NAME))
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

.

.

ProtoDataStore 的使用

Proto DataStore实现使用 DataStoreprotocol buffers 将键入的对象持久保存到磁盘。

protocol buffers(协议缓冲区)是一种用于序列化结构化数据的机制。使用请查阅参考资料里的文章,这里由于篇幅就不展开介绍了,敬请原谅。

Proto DataStore的简单使用

build.gradle 文件添加 Proto DataStore 的依赖

  • 添加Protobuf插件
  • 添加Protobuf和ProtoDataStore依赖项
  • 配置Protobuf
//添加Protobuf插件
plugins {
	id "com.google.protobuf" version "0.8.12"
}

dependencies {

  // Proto DataStore 的依赖
  implementation  "androidx.datastore:datastore-core:1.0.0-alpha04"
  implementation  "com.google.protobuf:protobuf-javalite:3.10.0"
}

//配置Protobuf
protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.10.0"
    }

    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option 'lite'
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

.

ProtoDataStore 的使用步骤如下:

1. 创建 user_prefs.proto 来编写存放结构数据的模板

注:
记得在 app/src/main下创建名为 proto 的文件夹,并在 app/src/main/proto目录中创建一个新文件下创建名为 user_prefs.proto 的文件 ;在 user_prefs.proto文件里编写数据的模板。

syntax = "proto3";

option java_package = "com.codelab.android.datastore";
option java_multiple_files = true;

message UserPreferences {
  //用于显示/隐藏已完成任务的过滤器
  bool show_completed = 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注:
UserPreferences类是在编译时从所生成的message在原文件中定义。确保您重建项目。

2. 创建序列化器

为了告诉 DataStore 如何读写在原型文件中定义的数据类型,我们需要实现一个Serializer。如果磁盘上没有数据,则序列化程序还定义要返回的默认值。

创建序列化器类 UserPreferencesSerializer

object UserPreferencesSerializer : Serializer<UserPreferences> {
    override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()
    override fun readFrom(input: InputStream): UserPreferences {
        try {
            return UserPreferences.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注:
如果UserPreferences找不到对象或相关方法,请清理并重建项目以确保Protobuf生成了对象。

3. 创建数据存储区对象

作用: 通过这个对象就可以进行数据的 读取 和 添加 。

private val dataStore: DataStore<UserPreferences> =
    context.createDataStore(
        fileName = "user_prefs.pb",
        serializer = UserPreferencesSerializer)
  • 1
  • 2
  • 3
  • 4

4. 向 Proto DataStore中添加数据

suspend fun saveData(completed: Boolean) {
    dataStore.updateData { preferences ->
        preferences.toBuilder().setShowCompleted(completed).build()
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5

注:
Proto DataStore 中是通过 dataStore.updateData() 写入数据,updateData 方法是个 suspend 函数,必须在协程中进行调用;

updateData()函数会通过原子读写修改操作以事务方式更新数据。一旦数据保留在磁盘上,协程将完成。

5. 从Proto DataStore读取数据

val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
  • 1

注:

从ProtoDataStore中读取数据的返回值类型是Flow<UserPreferences>
UserPreferences是在user_prefs.proto文件定义的 message对象。

6. 当读取数据时异常的处理

由于 DataStore 从文件读取数据,因此在读取数据有可能会引起 IOException数据读取异常。我们可以使用 catch 来捕获异常,并对异常进行相关处理。如下所示:

private val TAG: String = "数据读取失败"

val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
    .catch { exception ->

        //数据读取失败
        if (exception is IOException) {
			//捕获异常(对异常进行处理)
            Log.e(TAG, "Error reading sort order preferences.", exception)
		
			emit(UserPreferences.getDefaultInstance())

        } else {
            throw exception
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

从 SharedPreference 迁移数据

为了帮助迁移,DataStore定义了SharedPreferencesMigration该类。让我们在中创建数据存储区对象的时候可以使用migrations参数来实现数据的迁移。

迁移步骤:

1. 编写迁移规则

  1. 检查 UserPreferences 中的 sortOrder 值。
  2. 如果是SortOrder.UNSPECIFIED这样,则意味着我们需要从SharedPreferences中检索值。如果 SortOrder 缺少,则可以使用SortOrder.NONE默认值。
  3. 获得排序顺序后,我们必须将 UserPreferences 对象转换为 builder,设置排序顺序,然后通过调用再次构建对象build()。此更改不会影响其他字段。
  4. 如果sortOrder的值UserPreferences不是,SortOrder.UNSPECIFIED我们只能返回我们获得的当前数据,migrate因为迁移必须已经成功运行
String SharedPreference_name = "填入需要迁移的SharedPreference的名字";

private val sharedPrefsMigration = SharedPreferencesMigration(context,SharedPreference_name) { 
		//SharedPreferencesView:表示允许我们从SharedPreferences中检索数据
		sharedPrefs: SharedPreferencesView, 
		//UserPreferences:表示当前数据
		currentData: UserPreferences ->

        // 定义从SharedPreferences到UserPreferences的映射
        if (currentData.sortOrder == SortOrder.UNSPECIFIED) {
            currentData.toBuilder().setSortOrder(
                SortOrder.valueOf(
                    sharedPrefs.getString(
                        SORT_ORDER_KEY,SortOrder.NONE.name)!!
                )
            ).build()
        } else {
            currentData
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2. 在创建数据存储对象的时候使用定义好的迁移逻辑

migrations包含我们的实例的新列表分配给参数SharedPreferencesMigration

private val dataStore: DataStore<UserPreferences> = context.createDataStore(
    fileName = "user_prefs.pb",
    serializer = UserPreferencesSerializer,
    migrations = listOf(sharedPrefsMigration)
)
  • 1
  • 2
  • 3
  • 4
  • 5

通过以上两步就可以实现把数据从 SharedPreference 迁移到 ProtoDataStore 中,到处有关DataStore的使用就全部介绍完毕了,至于Protobuf语言 的语法请自行去学习,在这里就不做介绍了。

.

.

参考资料:

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

闽ICP备14008679号