赞
踩
Jetpack DataStore 是一种经过改进的新数据存储解决方案,旨在取代 SharedPreferences
。DataStore 基于 Kotlin 协程和 Flow 构建而成,提供以下两种不同的实现:
SharedPreferences
的一些缺点。此实现不需要预定义的架构,也不确保类型安全。功能 | SharedPreferences | PreferencesDataStore | ProtoDataStore |
---|---|---|---|
异步 API | ✅(仅用于通过监听器读取已更改的值) | ✅(通过 Flow 以及 RxJava 2 和 3 Flowable) | ✅(通过 Flow 以及 RxJava 2 和 3 Flowable) |
同步 API | ✅(但无法在界面线程上安全调用) | ❌ | ❌ |
可在界面线程上安全调用 | ❌ | ✅(这项工作已在后台移至 Dispatchers.IO) | ✅(这项工作已在后台移至 Dispatchers.IO) |
可以提示错误 | ❌ | ✅ | ✅ |
不受运行时异常影响 | ❌ | ✅ | ✅ |
包含一个具有强一致性保证的事务性 API | ❌ | ✅ | ✅ |
处理数据迁移 | ❌ | ✅ | ✅ |
类型安全 | ❌ | ❌ | ✅ 使用协议缓冲区 |
SharedPreferences的缺陷:
SharedPreferences
有一个看上去可以在界面线程中安全调用的同步 API,但是该 API 实际上执行磁盘 I/O 操作。此外,apply()
会阻断 fsync()
上的界面线程。每次有服务启动或停止以及每次 activity 在应用中的任何地方启动或停止时,系统都会触发待处理的 fsync()
调用。界面线程在 apply()
调度的待处理 fsync()
调用上会被阻断,这通常会导致 ANR
。
SharedPreferences
还会将解析错误作为运行时异常抛出。
如果您当前在使用 SharedPreferences
存储数据,请考虑迁移到 DataStore
。
注意:如果您需要支持大型或复杂数据集、部分更新或参照完整性,请考虑使用 Room,而不是 DataStore。DataStore 的目的是存储简单的小型数据集, 但不支持部分更新或引用完整性。
为了正确使用 DataStore,请始终谨记以下规则:
请勿在同一进程中为给定文件创建多个 DataStore 实例,否则会破坏所有 DataStore
功能。如果给定文件在同一进程中有多个有效的 DataStore
,DataStore
在读取或更新数据时将抛出
IllegalStateException
。
DataStore 的通用类型必须不可变。更改 DataStore
中使用的类型会导致 DataStore
提供的所有保证失效,并且可能会造成严重的、难以发现的 bug。强烈建议您使用可保证不可变性、具有简单的 API
且能够高效进行序列化的协议缓冲区。
切勿在同一个文件中混用 SingleProcessDataStore 和 MultiProcessDataStore。如果您打算从多个进程访问 DataStore,请始终使用 MultiProcessDataStore
。
Preference DataStore
API 类似于 SharedPreferences
,但与后者相比存在一些显著差异:
Flow
添加依赖:
dependencies {
implementation("androidx.datastore:datastore-preferences:1.0.0")
}
Preferences DataStore 实现使用 DataStore 和 Preferences 类将简单的键值对保留在磁盘上。
使用由 preferencesDataStore
提供的属性委托来创建 Datastore<Preferences>
实例。只需在 Kotlin 文件顶层调用该实例一次,便可在应用的所有其余部分通过此属性访问该实例。这样可以更轻松地将 DataStore
保留为单例。
private const val USER_PREFERENCES_NAME = "user_preferences"
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME
)
由于 Preferences DataStore 不使用预定义的架构,因此必须使用相应的键类型函数为需要存储在 DataStore<Preferences>
实例中的每个值定义一个键。例如,如需为 int
值定义一个键,请使用 intPreferencesKey()
。然后,使用 DataStore.data
属性,通过 Flow
提供适当的存储值。
private object PreferencesKeys {
val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
}
val counterFlow: Flow<Int> = context.dataStore.data.map {
preferences ->
preferences[EXAMPLE_COUNTER] ?: 0
}
val completeFlow: Flow<Boolean> = context.dataStore.data.map {
preferences ->
preferences[SHOW_COMPLETED] ?: false
}
如果要读取的内容很多,可以定义一个data class
来存储,在dataStore.data.map
中返回该数据类对象即可:
data class UserPreferences(val count: Int, val show: Boolean)
val userPreferenceFlow = context.dataStore.data.map {
preferences ->
val count = preferences[EXAMPLE_COUNTER] ?: 0
val show = preferences[SHOW_COMPLETED] ?: false
UserPreferences(count, show)
}
当 DataStore 从文件读取数据时,如果读取数据期间出现错误,系统会抛出 IOExceptions
。我们可以通过以下方式处理这些事务:在 map()
之前使用 catch()
Flow 运算符,并且在抛出的异常是 IOException
时发出 emptyPreferences()
。如果出现其他类型的异常,最好重新抛出该异常。
val userPreferenceFlow = context.dataStore.data .catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data if (exception is IOException) { emit(emptyPreferences()) } else { throw exception } }.map { preferences -> val count = preferences[EXAMPLE_COUNTER] ?: 0 val show = preferences[SHOW_COMPLETED] ?: false UserPreferences(count, show) }
也可以选择在外面包裹一层 try-catch
进行处理。
Preferences DataStore 提供了一个 edit()
函数,用于以事务方式更新 DataStore 中的数据。该函数的 transform
参数接受代码块,您可以在其中根据需要更新值。转换块中的所有代码均被视为单个事务。
suspend fun updateShowCompleted(showCompleted: Boolean) {
try {
context.dataStore.edit {
preferences ->
val currentCounterValue = preferences[EXAMPLE_COUNTER] ?: 0
preferences[EXAMPLE_COUNTER] = currentCounterValue + 1
preferences[SHOW_COMPLETED] = showCompleted
}
} catch (e: IOException) {
println(e)
}
}
如果在读取或写入磁盘时发生错误,edit()
可能会抛出 IOException
。如果转换块中出现任何其他错误,edit()
将抛出异常。
下面是一个在 Compose 中使用包含 ViewModel 、Repository 和 DataStore 的完整示例:
// DataStore.kt private const val USER_PREFERENCES_NAME = "user_preferences" private val Context.dataStore by preferencesDataStore( name = USER_PREFERENCES_NAME ) private object PreferencesKeys { val SHOW_COMPLETED = booleanPreferencesKey("show_completed") val EXAMPLE_COUNTER = intPreferencesKey("example_counter") } data class UserPreferences(val count: Int, val show: Boolean) class UserPreferencesRepository(val context: Context) { val userPreferenceFlow = context.dataStore.data .catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data if (exception is IOException) { emit(emptyPreferences()) } else { throw exception } }.map { preferences -> val count = preferences[EXAMPLE_COUNTER] ?: 0 val show = preferences[SHOW_COMPLETED] ?: false UserPreferences(count, show) } suspend fun updateShowCompleted(showCompleted: Boolean) { try { context.dataStore.edit { preferences -> val currentCounterValue = preferences[EXAMPLE_COUNTER]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。