其他
关于Jetpack DataStore的八点疑问
本文作者
作者:小鱼人爱编程
链接:
https://juejin.cn/post/7235801811524763705
本文由作者授权发布。
前言
引入依赖
implementation("androidx.datastore:datastore-preferences:1.0.0")
使用DataStore存取数据
存数据
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "test")
val myNameKey = stringPreferencesKey("name")
val myAgeKey = intPreferencesKey("age")
suspend fun saveData() {
context.dataStore.edit {
//给不同的key赋值
it[myNameKey] = "fish"
it[myAgeKey] = 18
}
}
取数据
suspend fun queryData() {
context.dataStore.data.collect {
it.asMap().forEach {
println("${it.key.name}, ${it.value}")
}
}
}
//打印结果:
I/System.out: name, fish
I/System.out: age, 18
可以看出存取过程和SharedPreferences很相似,只是key的构造有些差异。
Boolean、Double、Float、Int、Long、String、Set
dataStore.data
1. 存取数据的闭包的执行是在当前协程(调用saveData/queryData的协程)里执行的。 2. 假若当前是在主线程发起的存取动作,那么闭包将在主线程执行。
总的来说:借助于协程的特性,DataFlow存取数据并不耗时。
DataStore Flow是冷流还是热流?
//定义热流
private val downstreamFlow = MutableStateFlow(UnInitialized as State<T>)
override val data: Flow<T> = flow {
val currentDownStreamFlowState = downstreamFlow.value
if (currentDownStreamFlowState !is Data) {
actor.offer(SingleProcessDataStore.Message.Read(currentDownStreamFlowState))
}
emitAll(
//监听热流变化
downstreamFlow.dropWhile {
//满足条件则丢弃数据
if (currentDownStreamFlowState is Data<T> ||
currentDownStreamFlowState is Final<T>
) {
//不满足则继续流向map
false
} else {
//判断是否满足
it === currentDownStreamFlowState
}
}.map {
when (it) {
//根据类型,返回不同的值
is ReadException<T> -> throw it.readException
is Final<T> -> throw it.finalException
//正常的返回值
is Data<T> -> it.value
is UnInitialized -> error(
"This is a bug in DataStore. Please file a bug at: " +
"https://issuetracker.google.com/issues/new?" +
"component=907884&template=1466542"
)
}
}
)
}
1. dataStore.data 是Flow,它是冷流。 2. dataStore.data 里依靠downstreamFlow(热流)持续监听数据的变化。 3. 因此dataStore.data 可以持续监听数据的变化,当DataStore里数据发生变化时将会回调闭包。
DataStore Flow与其它Flow的差异
suspend fun queryData2() {
val flow = flow {
emit("hello")
}
flow.collect {
println(it)
}
println("normal flow end")
}
suspend fun queryData() {
context.dataStore.data.collect {
it.asMap().forEach {
println("${it.key.name}, ${it.value}")
}
}
println("dataStore flow end")
}
"normal flow end"会打印,而"dataStore flow end"永远没有机会执行
其实这也很容易想到:若是DataStore Flow的collect退出了,它就无法监听数据变化了。
存取影响范围
val myScoreKey = floatPreferencesKey("score")
suspend fun queryDataV2() {
context.dataStore.data.map {
//只关心分数的变化
it[myScoreKey]
}..collect {
println("$it")
}
}
suspend fun saveData2() {
context.dataStore.edit {
//只修改分数
it[myScoreKey] = 99f
}
}
suspend fun saveData2() {
context.dataStore.edit {
//只修改分数
it[myNameKey] = "fish is perfect"
}
}
DataStore更新和监听都是针对单个文件的全部字段。
存相同的数值
suspend fun saveData2() {
context.dataStore.edit {
//只修改分数
it[myNameKey] = "fish is perfect"
}
}
答案:不会,因为每次更新数据之前都会比对和上一次的数据是否一致,若是一致则不会再写入文件,当然也不会产 生数据变化的通知。
suspend fun saveData2() {
context.dataStore.edit {
//只修改分数
it[myNameKey] = "fish is perfect3"
}
}
GlobalScope.launch(Dispatchers.IO) {
myDataStore.saveData2()
}
GlobalScope.launch(Dispatchers.Main) {
myDataStore.saveData2()
}
1. 不管是读还是写,每次调用当做一次任务,若当前没有协程执行任务,则开启新协程执行任务,新协程跑在IO线程里。 2. 若是有任务在执行,则仅仅只是将任务加入到队列里,调用者返回;当上个任务执行完毕再执行该任务。 3. 因此单个DataStore读写是线程安全的。
此处的策略和线程池的实现类似,有需要的可以查看过往关于线程池设计的文章。
class MyDataStore(val context: Context) {
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "test")
suspend fun saveData2() {
context.dataStore.edit {
//只修改分数
it[myNameKey] = "fish is perfect3"
}
}
}
lifecycleScope.launch {
MyDataStore(this@DataStoreActivity).saveData2()
}
class MyDataStore(val context: Context) {
companion object {
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(MyDataStore.javaClass.name)
}
}
通过静态变量确保只有一个实例。
使用协程挂起函数存取数据,不阻塞UI,不像SharedPreferences可能会引发ANR。
1. 调用者没有协程环境(针对老的代码)。 2. 调用者需要同步访问DataStore数据。
1. 提供一个同步方法,用于获取外界关注的状态,而内部监听Flow的变化,有变化就同步到状态里, 如此一来,对于协程和Flow的使用控制在内部,外部仅仅只需要获取内存状态即可。 2. 提供一个同步方法,直接获取数据。
val myNameKey = stringPreferencesKey("name")
fun getName():String? {
return runBlocking {
context.dataStore.data.map {
it[myNameKey]
}.first() as? String
}
}
对于第一次读取耗时问题,我们可以进行预加载,比如在某个时机提前加载数据。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
点击 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!