DataStore的基础用法
文章目录
- 0. 简介
- 1. SP的缺点
- 2. DataStore的基础用法
- a. DataStore怎么写
- b. DataStore怎么读
- 3. 从SP迁移到DataStore
- 4. DataStore的封装类
- 5. 个人结论
0. 简介
Google在推出JetPack
组件以来,一直推荐我们使用DataStore
组件替代到我们第一天学android就知道的SharedPreferences
组件,原因很简单,因为当年的SharedPreferences
存在居多的问题,DataStore
就是为了解决这些问题而来的。
1. SP的缺点
至于 SP
到底存在哪些问题,我们可以直接查看 DataStore
源码上的注释:
- Synchronous API encourages StrictMode violations
- apply() and commit() have no mechanism of signalling errors
- apply() will block the UI thread on fsync()
- Not durable – it can returns state that is not yet persisted
- No consistency or transactional semantics
- Throws runtime exception on parsing errors
- Exposes mutable references to its internal state
用我们蹩脚的英语逐字逐句的翻译一遍:
- 同步的API鼓励违反StrictMode模式
- apply()和commit()方法没有错误信号机制
- apply()方法将界面重绘时会在阻塞UI线程
- 不耐用 - 它可以返回状态,但是并不能将状态持久化
- 没有一致性或者事物语义
- 解析出现错误时,直接抛出运行时异常
- 在其内部的状态中,暴露其可变的引用
老外写的问题直译过来一般都比较难懂,除非讲的很简单清楚。那么我就说两句人话,大概说一下我所认为的 SP
所存在的问题吧:
- 不支持跨进程,使用
MODE_MULTI_PROCESS
模式也没鸟用。而且在跨进程中,频繁的读写可能导致数据损坏或者丢失;- 懒加载模式下读取
SP
文件,可能会导致 getXXX() 阻塞。所以建议提前异步初始化SP
;sp
文件的中的数据全部都保存在内存中,所以SP
对大数据量少儿不宜edit()
方法每次都会新建一个EditorImpl
对象。建议一次edit()
,多次putXXX
;- 无论是
commit()
还是apply()
,针对任何修改都是全量写入。这种情况下,对于高频的修改配置项存放在单独的SP
文件中;commit()
同步保存,有返回值;apply()
异步保存,无返回值。onPause()
onReceive()
方法中使用异步写操作执行完成,可能会造成卡顿或者ANR。
当然这里并不是把SP
贬得一无是处啊,正所谓存在即合理,当我们不涉及到跨进程,并且存储数据量比较少的情况下,SP
还是相当不错的选择。
2. DataStore的基础用法
首先需要声明的一点是,DataStore
存在两个版本的,一种是类似于SP
,基于普通文件的读写;一种是基于Google protobuf
模式的,这里的 protobuf
是 Google
自研的一种数据结构,平时用到的也比较少,我以前博客里面也写过类似的,这里就先只介绍第一种基于文件的逻辑:
下面介绍一下 DataStore
的基础用法:
首先需要引入:
implementation("androidx.datastore:datastore:1.0.0")
首先需要明确一点,既然我们的DataStore
是兼容当前使用的SP
的,那么它就应该支持SP
的存储类型,而且我们也知道SP
支持的数据类型为Int
,Long
,Float
,Boolean
,String
和StringSet
;此时DataStore
不仅支持以上六种数据结构,还支持一种额外的Double
类型。
创建一个DataStore
对象:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "dataStore_data")
首先我们需要读取一个Int
型的对象:
val keyName = intPreferencesKey("example_counter")
val keyNameFlow: Flow<Int> = context.dataStore.data.map { preferences -> preferences[keyName] ?: 0
}
然后我们写入一个 Int
型的对象:
val keyName = intPreferencesKey("example_counter")suspend fun incrementCounter() {context.dataStore.edit { settings ->val currentCounterValue = settings[keyName] ?: 0settings[keyName] = currentCounterValue + 1}
}
第一眼看上去很懵逼,写的什么玩意儿啊。没错,我学 DataStore
的第一天也是这么想法,SP
比这玩意儿香一万倍都不止啊,这么多新的东西我不知道,而且感觉写起来也是很拉跨,写一个简单的存储这么多代码。
首先,的确一个新的知识点出来,大家内心肯定是抗拒的,因为要去理解和实践,这个本身就比较耗时间和精力。但是大家都一样,所以还是需要去迎接变化。
首先,我们按照简单的来,我们都知道SP
是基于XML
文件的Key-Value
结构,那么 DataStore
作为它的兼容类,也必然兼容这种Key-Value
结构。那么DataStore
的Key
是String
型的么?然而并不是,它是一种Preferences.Key
类型,具体类型为:androidx.datastore.preferences.core.Preferences.Key
, 可以分为以下几种类型:
- intPreferencesKey -> Preferences.Key<Int> 保存
Int
类型数据- doublePreferencesKey -> Preferences.Key<Double> 保存
Double
类型数据- stringPreferencesKey -> Preferences.Key<String> 保存
String
类型数据- booleanPreferencesKey ->Preferences.Key<Boolean> 保存
Boolean
类型数据- floatPreferencesKey -> Preferences.Key<Float> 保存
Float
类型数据- longPreferencesKey -> Preferences.Key<Long> 保存
Long
类型数据- stringSetPreferencesKey -> Preferences.Key<Set<String>> 保存
Set<String>
类型数据
有了Key
之后,我们需要看看DataStore
如果存储和读取数据的。
a. DataStore怎么写
SP
有Editor
,同理DataStore
也有edit
方法:
public suspend fun DataStore<Preferences>.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) }}
}
首先它是一个suspend
函数,只能在协程体中运行,每当遇到 suspend
函数以挂起的方式运行时,并不会阻塞主线程运行。
既然是suspend
函数,那么我们就可以有同步
和异步
的方式对数据进行写入:
同步方式
:
private suspend fun saveSyncIntData(key : String, value:Int) {globalDataStore.edit { mutablePreferences -> mutablePreferences[intPreferencesKey(key)] = value }
}
异步方式
:
这个就很简单了,可以随意发挥了,在同步方法上套一个runBlocking
就行了:
private fun saveIntData(key: String, value:Int) = runBlocking { saveSyncIntData(key,value) }
b. DataStore怎么读
按照以上的惯例,肯定也会存在同步读取
和异步读取
的两种方法。首先需要明确一点,DataStore
的data
返回的是Flow
类型,Flow
是一种流式接口,类似于RxJava
中的 Observable
那样,存在很多操作符可以对数据进行变换,时间允许的情况下,可以写一篇关于Flow
文章。
首先我们获取到同步的读
:
private fun readSyncIntData(key: String, defaultValue: Int) : Flow<Int> = dataStore.data.catch {if(it is IOException) {it.printStackTrace()emit(emptyPreferences())} else {throw it}}.map { it[intPreferencesKey(key)] ?: defaultValue }
对代码进行解读一下, dataStore.data
返回类型是Flow
类型,对Flow
进行catch
检查是否存在异常,然后map
转换一下,然后得到Flow<Int>
,最终并返回。
写完了同步的读
,那么异步的读
为:
private fun readIntData(key: String, defaultValue : Int) : Int {var resultValue = defaultValuerunBlocking {dataStore.data.first {resultValue = it[intPreferencesKey(key)] ?: resultValuetrue}}return resultValue
}
异步的读取直接返回了具体类型的数据,这里的first
操作符是取第一个的意思。
基本上,我们对DataStore
的操作有了一个简单的了解,重要的还是自己去实践,不难也不算容易。
3. 从SP迁移到DataStore
大概也就分两个步骤:
- 需要一个
SharedPreferencesMigration
,这个迁移类并不算难,也就需要你传入Context
和SP
的文件名即可:
val Context.dataStore : DataStore<Preferences> by preferencesDataStore(name = "dataStore_setting",produceMigrations = { context ->listOf( SharedPreferencesMigration(context, "sp_name"))})
- 当
dataStore
生成完成之后,需要执行一个读或者写的操作,SharedPreferences
的数据将会被迁移到dataStore
中,同时SharedPreferences
文件也将会被删除。
使用
SP
的文件夹
迁移到
DataStore
的文件夹
可以看到SP
文件被删除了,然后dataStore
的文件目录为/data/data/package_name/files/xxx.preferences_pb
4. DataStore的封装类
为了方便操作,我这边封装了DataStore
的逻辑,读写起来会更方便一点,方法部分代码为:
我们使用时也很简单,直接代码为:
DataStoreUtils.putData("int_value",100)
DataStoreUtils.putData("long_value",100L)
DataStoreUtils.putData("float_value",100.0f)
DataStoreUtils.putData("double_value",100.00)
DataStoreUtils.putData("boolean_value",true)
DataStoreUtils.putData("string_value","hello world")val intValue = DataStoreUtils.getData("int_value", 0)
val longValue = DataStoreUtils.getData("long_value", 0L)
val floatValue = DataStoreUtils.getData("float_value", 0.0f)
val doubleValue = DataStoreUtils.getData("double_value", 0.00)
val booleanValue = DataStoreUtils.getData("boolean_value", false)
val stringValue = DataStoreUtils.getData("string_value", "hello")
当然,这只是异步的读取/存储方式,当然我们还有同步的获取方式:
lifecycle.coroutineScope.launch {// 读取DataStoreUtils.getSyncData("int_value",0).collect(object : FlowCollector<Int> {override suspend fun emit(value: Int) {Log.d("TAG","get sync data : $value")}})// 写入DataStoreUtils.putSyncData("int_value", 1)}
当然,具体的源码可以看这个了.
5. 个人结论
总体来说,DataStore
如果高度封装,其实使用方式上和SP
基本上没什么区别,它解决了SP
所存在的诟病,但是就目前而言,对它的性能还是未知的,这个可能需要后续的线上检验了,当然谷爹出品的东西,应该没什么太大的问题。当然了,学习DataStore
其实对Kotlin
还是有很高的门槛的,其中协程
,高阶函数
、Flow
等相关知识点还是存在一个相当陡峭的学习坡度的。
DataStore的基础用法相关推荐
- Pandas基础用法合集(中文官档)
本节介绍 pandas 数据结构的基础用法.下列代码创建示例数据对象: In [1]: index = pd.date_range('1/1/2000', periods=8) In [2]: s = ...
- Promise基础用法
什么是Promise? Promise是用来处理异步的; Promise就是承诺,对未来的承诺; 所谓的Promise(承诺),里面保存着未来才会结束的事件的结果; Promise是异步编程的一种解决 ...
- pandas object转float_数据分析篇 | Pandas基础用法6【完结篇】
这是最后一篇,至此Pandas系列终于连载完了,有需要的也可以看看前面6篇,尽请收藏. 数据分析篇 | Pandas 概览 数据分析篇 | Pandas基础用法1数据分析篇 | Pandas基础用法2 ...
- dubbo官方文档中文_Pandas中文官方文档:基础用法5
本文授权转载自Python大咖谈禁止二次转载 大家好,我是老表 阅读文本大概需要 16 分钟 建议从头开始学习,本系列前四篇 Pandas中文官方文档:基础用法1 Pandas中文官方文档:基础用法2 ...
- struct用法_精讲响应式webclient第1篇-响应式非阻塞IO与基础用法
笔者在之前已经写了一系列的关于RestTemplate的文章,如下: 精讲RestTemplate第1篇-在Spring或非Spring环境下如何使用 精讲RestTemplate第2篇-多种底层HT ...
- Promise详解(一) ----基础用法
什么是Promise Promise是由社区最早提出并实现的,后加入到es6规范中.为更好处理javascript异步问题,用于解决其多次异步回掉嵌套问题.下面先尝试下promise的基础用法吧. 创 ...
- ORACLE的基础用法
ORACLE的基础用法 转 自:http://blog.163.com/kelly_19831017/blog/static/6196787020083133348873/ sqlplus sys/o ...
- MongoDB 基础用法及学习笔记
MongoDB 基础用法 环境配置与安装 安装 查看MongoDB版本 启动MongoDB服务 检查服务状态 启动服务 打开配置文件,连接MongoDB 查看数据列表 退出 MongoDB 连接 Mo ...
- Python语言学习:python语言的特点、入门、基础用法之详细攻略
Python语言学习:python语言的特点.入门.基础用法之详细攻略 相关内容 Python 基础教程 目录 python语言的特点 python语言的入门 python语言的基础用法 python ...
- pandas库Series使用和ix、loc、iloc基础用法
1. pandas库Series基础用法: 直接贴出用例: 1. 构造/初始化Series的3种方法: (1)用列表list构建Series import pandas as pd my_list=[ ...
最新文章
- SAP保存操作记录CDHDR和CDPOS表
- 如何处理SAP Launchpad上tile打不开的问题
- java 默认参数_Java 方法的参数可以有默认值吗?
- oracle11g和12c安装区别,Oracle下载与Oracle安装图解(Oracle19c,Oracle18c,Oracle12c,Oracle11g)...
- 图论算法(五)--求解割点、割边(JAVA)
- menu---A.2-基本算法实例
- APP发布Xcode7
- Linux下导入SQL文件
- 不要再被Python洗脑了!!
- 【java学习之路】(java SE篇)(练习)集合练习:经典例题
- centos6.4-x86-64系统更新系统自带Apache Http Server
- 前端JS/TS面试题
- 单片机——LED点阵
- ios silk to MP3
- python当前时间减一年_python当前时间减一年_Python 日期时间datetime 加一天,减一天,加减一小时一分钟,加减一年......
- 计算机中级改进的具体措施,改进职高计算机教学的几点措施
- docker安装UnlockMusic(音乐格式转换工具 )
- 常见的web服务器有哪些
- Typo: In word ‘localstime‘ less... (Ctrl+F1) Inspection info: Spellchecker inspection helps locate
- OC语言基础内容 类的定义 第一个OC