在官方尚未出手之前,存储键值对等小型数据集可能普遍采用两种方式,SharedPreferences或是MMKV(如果您需要支持大型或复杂数据集、部分更新或参照完整性,请考虑使用 Room,而不是 DataStore。DataStore 非常适合简单的小型数据集,不支持部分更新或参照完整性。) MMKV这次暂时不提及,因为DatStore本身对比的也就是SharedPreferences,而且官方也是明确的建议我们迁移到DataStore。 DataStore包含了两种实现方式:
首先我们来看官方的一张对比
功能 | SharedPreferences | PreferencesDataStore | ProtoDataStore |
异步 API | ✅(仅用于通过监听器读取已更改的值) | ✅(通过 Flow 以及 RxJava 2 和 3 Flowable) | ✅(通过 Flow 以及 RxJava 2 和 3 Flowable) |
同步 API | ✅(但无法在界面线程上安全调用) | ❌ | ❌ |
可在界面线程上安全调用 | ❌1 | ✅(这项工作已在后台移至 Dispatchers.IO) | ✅(这项工作已在后台移至 Dispatchers.IO) |
可以提示错误 | ❌ | ✅ | ✅ |
不受运行时异常影响 | ❌2 | ✅ | ✅ |
包含一个具有强一致性保证的事务性 API | ❌ | ✅ | ✅ |
处理数据迁移 | ❌ | ✅ | ✅ |
类型安全 | ❌ | ❌ | ✅ 使用协议缓冲区 |
我们先暂时只看PreferencesDataStore和SharedPreferences 首先同步API和异步API这两点区别是没有问题的。 SharedPreferences:
详细的可以查看SharedPreferences.Editor接口提供的注释,具体的实现在SharedPreferencesImpl.EditorImpl我这里就不贴源码了。 回到DataStore,PreferencesDataStore本身是基于携程Flow来实现的,所以异步API这点没有任何问题,不过至于同步的使用方式,放到后面来说,我们先看普遍的异步使用方式。我就不一一复述了。
private const val USER_PREFERENCES_NAME = "user_preferences"
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME
)
首先是通过委托拿到DataStore
public fun preferencesDataStore(
name: String,
corruptionHandler: ReplaceFileCorruptionHandler? = null,
produceMigrations: (Context) -> List> = { listOf() },
scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): ReadOnlyProperty> {
return PreferenceDataStoreSingletonDelegate(name, corruptionHandler, produceMigrations, scope)
}
internal class PreferenceDataStoreSingletonDelegate internal constructor(
private val name: String,
private val corruptionHandler: ReplaceFileCorruptionHandler?,
private val produceMigrations: (Context) -> List>,
private val scope: CoroutineScope
) : ReadOnlyProperty> {
private val lock = Any()
@GuardedBy("lock")
@Volatile
private var INSTANCE: DataStore? = null
override fun getValue(thisRef: Context, property: KProperty<*>): DataStore {
return INSTANCE ?: synchronized(lock) {
if (INSTANCE == null) {
val applicationContext = thisRef.applicationContext
INSTANCE = PreferenceDataStoreFactory.create(
corruptionHandler = corruptionHandler,
migrations = produceMigrations(applicationContext),
scope = scope
) {
applicationContext.preferencesDataStoreFile(name)
}
}
INSTANCE!!
}
}
}
本质上还是走的PreferenceDataStoreFactory.create()创建,是一个非常标准的双重检查锁单例。 来看一下create()函数的参数吧
推荐做法也是通过PreferenceDataStoreFactory来创建DataStore实例并作为单例注入需要它的类中。
dataStore.data
.catch { exception ->
// 有异常抛出
if (exception is IOException) {
// 使用默认空值
emit(emptyPreferences())
} else {
// 其他异常则继续抛出
throw exception
}
}.map { preferences ->
// 数据转化
}.collect {
// 收集数据
}
emptyPreferences()可以参考上面提到的官方教学示例,里面会详细介绍PreferenceData的KV 可以看出dataStore返回的是一个Flow,你可以很方便的转换成你所需要的数据。
val INT_KEY = intPreferencesKey("int_key")
dataStore.edit { preferences ->
preferences[INT_KEY] = 1
}
// 调用
public suspend fun DataStore.edit(
transform: suspend (MutablePreferences) -> Unit
): Preferences {
return this.updateData {
// It's safe to return MutablePreferences since we freeze it in
// PreferencesDataStore.updateData()
it.toMutablePreferences().apply { transform(this) }
}
}
直接调用edit函数,不过要注意的是edit可能会抛出异常。 同时还有两点需要注意:
总的来说还是很推荐使用DataStore,与协程的搭配,用起来也是非常的便利,至于PB协议的ProtoDataStore,可以参考官方的示例来实践,差别主要是还是集中在PB文件的处理。
作者:Lowae
链接:https://juejin.cn/post/7109395564789235720
| 留言与评论(共有 0 条评论) “” |