文章目录

  • 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源码上的注释:

  1. Synchronous API encourages StrictMode violations
  2. apply() and commit() have no mechanism of signalling errors
  3. apply() will block the UI thread on fsync()
  4. Not durable – it can returns state that is not yet persisted
  5. No consistency or transactional semantics
  6. Throws runtime exception on parsing errors
  7. Exposes mutable references to its internal state

用我们蹩脚的英语逐字逐句的翻译一遍:

  1. 同步的API鼓励违反StrictMode模式
  2. apply()和commit()方法没有错误信号机制
  3. apply()方法将界面重绘时会在阻塞UI线程
  4. 不耐用 - 它可以返回状态,但是并不能将状态持久化
  5. 没有一致性或者事物语义
  6. 解析出现错误时,直接抛出运行时异常
  7. 在其内部的状态中,暴露其可变的引用

老外写的问题直译过来一般都比较难懂,除非讲的很简单清楚。那么我就说两句人话,大概说一下我所认为的 SP所存在的问题吧:

  1. 不支持跨进程,使用 MODE_MULTI_PROCESS模式也没鸟用。而且在跨进程中,频繁的读写可能导致数据损坏或者丢失;
  2. 懒加载模式下读取SP文件,可能会导致 getXXX() 阻塞。所以建议提前异步初始化 SP;
  3. sp文件的中的数据全部都保存在内存中,所以 SP对大数据量少儿不宜
  4. edit()方法每次都会新建一个EditorImpl对象。建议一次edit(),多次putXXX;
  5. 无论是 commit()还是 apply(),针对任何修改都是全量写入。这种情况下,对于高频的修改配置项存放在单独的SP文件中;
  6. commit()同步保存,有返回值;apply()异步保存,无返回值。
  7. onPause() onReceive()方法中使用异步写操作执行完成,可能会造成卡顿或者ANR。

当然这里并不是把SP贬得一无是处啊,正所谓存在即合理,当我们不涉及到跨进程,并且存储数据量比较少的情况下,SP还是相当不错的选择。

2. DataStore的基础用法

首先需要声明的一点是,DataStore存在两个版本的,一种是类似于SP,基于普通文件的读写;一种是基于Google protobuf模式的,这里的 protobufGoogle自研的一种数据结构,平时用到的也比较少,我以前博客里面也写过类似的,这里就先只介绍第一种基于文件的逻辑:

下面介绍一下 DataStore的基础用法:

首先需要引入:

 implementation("androidx.datastore:datastore:1.0.0")

首先需要明确一点,既然我们的DataStore是兼容当前使用的SP的,那么它就应该支持SP的存储类型,而且我们也知道SP支持的数据类型为Int,Long,Float,Boolean,StringStringSet;此时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结构。那么DataStoreKeyString型的么?然而并不是,它是一种Preferences.Key类型,具体类型为:androidx.datastore.preferences.core.Preferences.Key, 可以分为以下几种类型:

  1. intPreferencesKey -> Preferences.Key<Int> 保存Int类型数据
  2. doublePreferencesKey -> Preferences.Key<Double> 保存Double类型数据
  3. stringPreferencesKey -> Preferences.Key<String> 保存String类型数据
  4. booleanPreferencesKey ->Preferences.Key<Boolean> 保存Boolean类型数据
  5. floatPreferencesKey -> Preferences.Key<Float> 保存Float类型数据
  6. longPreferencesKey -> Preferences.Key<Long> 保存Long类型数据
  7. stringSetPreferencesKey -> Preferences.Key<Set<String>> 保存Set<String>类型数据

有了Key之后,我们需要看看DataStore如果存储和读取数据的。

a. DataStore怎么写

SPEditor,同理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怎么读

按照以上的惯例,肯定也会存在同步读取异步读取的两种方法。首先需要明确一点,DataStoredata返回的是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

大概也就分两个步骤:

  1. 需要一个 SharedPreferencesMigration,这个迁移类并不算难,也就需要你传入ContextSP的文件名即可:
val Context.dataStore : DataStore<Preferences> by preferencesDataStore(name = "dataStore_setting",produceMigrations = { context ->listOf( SharedPreferencesMigration(context, "sp_name"))})
  1. 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的基础用法相关推荐

  1. Pandas基础用法合集(中文官档)

    本节介绍 pandas 数据结构的基础用法.下列代码创建示例数据对象: In [1]: index = pd.date_range('1/1/2000', periods=8) In [2]: s = ...

  2. Promise基础用法

    什么是Promise? Promise是用来处理异步的; Promise就是承诺,对未来的承诺; 所谓的Promise(承诺),里面保存着未来才会结束的事件的结果; Promise是异步编程的一种解决 ...

  3. pandas object转float_数据分析篇 | Pandas基础用法6【完结篇】

    这是最后一篇,至此Pandas系列终于连载完了,有需要的也可以看看前面6篇,尽请收藏. 数据分析篇 | Pandas 概览 数据分析篇 | Pandas基础用法1数据分析篇 | Pandas基础用法2 ...

  4. dubbo官方文档中文_Pandas中文官方文档:基础用法5

    本文授权转载自Python大咖谈禁止二次转载 大家好,我是老表 阅读文本大概需要 16 分钟 建议从头开始学习,本系列前四篇 Pandas中文官方文档:基础用法1 Pandas中文官方文档:基础用法2 ...

  5. struct用法_精讲响应式webclient第1篇-响应式非阻塞IO与基础用法

    笔者在之前已经写了一系列的关于RestTemplate的文章,如下: 精讲RestTemplate第1篇-在Spring或非Spring环境下如何使用 精讲RestTemplate第2篇-多种底层HT ...

  6. Promise详解(一) ----基础用法

    什么是Promise Promise是由社区最早提出并实现的,后加入到es6规范中.为更好处理javascript异步问题,用于解决其多次异步回掉嵌套问题.下面先尝试下promise的基础用法吧. 创 ...

  7. ORACLE的基础用法

    ORACLE的基础用法 转 自:http://blog.163.com/kelly_19831017/blog/static/6196787020083133348873/ sqlplus sys/o ...

  8. MongoDB 基础用法及学习笔记

    MongoDB 基础用法 环境配置与安装 安装 查看MongoDB版本 启动MongoDB服务 检查服务状态 启动服务 打开配置文件,连接MongoDB 查看数据列表 退出 MongoDB 连接 Mo ...

  9. Python语言学习:python语言的特点、入门、基础用法之详细攻略

    Python语言学习:python语言的特点.入门.基础用法之详细攻略 相关内容 Python 基础教程 目录 python语言的特点 python语言的入门 python语言的基础用法 python ...

  10. pandas库Series使用和ix、loc、iloc基础用法

    1. pandas库Series基础用法: 直接贴出用例: 1. 构造/初始化Series的3种方法: (1)用列表list构建Series import pandas as pd my_list=[ ...

最新文章

  1. SAP保存操作记录CDHDR和CDPOS表
  2. 如何处理SAP Launchpad上tile打不开的问题
  3. java 默认参数_Java 方法的参数可以有默认值吗?
  4. oracle11g和12c安装区别,Oracle下载与Oracle安装图解(Oracle19c,Oracle18c,Oracle12c,Oracle11g)...
  5. 图论算法(五)--求解割点、割边(JAVA)
  6. menu---A.2-基本算法实例
  7. APP发布Xcode7
  8. Linux下导入SQL文件
  9. 不要再被Python洗脑了!!
  10. 【java学习之路】(java SE篇)(练习)集合练习:经典例题
  11. centos6.4-x86-64系统更新系统自带Apache Http Server
  12. 前端JS/TS面试题
  13. 单片机——LED点阵
  14. ios silk to MP3
  15. python当前时间减一年_python当前时间减一年_Python 日期时间datetime 加一天,减一天,加减一小时一分钟,加减一年......
  16. 计算机中级改进的具体措施,改进职高计算机教学的几点措施
  17. docker安装UnlockMusic(音乐格式转换工具 )
  18. 常见的web服务器有哪些
  19. Typo: In word ‘localstime‘ less... (Ctrl+F1) Inspection info: Spellchecker inspection helps locate
  20. OC语言基础内容 类的定义 第一个OC

热门文章

  1. 一位考研学长的走心经验分享
  2. 通过高德地图获取 经纬度 以及地理位置等
  3. IP的子网和超网划分
  4. html 之 img hspace 和 vspace 属性
  5. hspace在表格中无效吗html,HTML代码汇总详解
  6. Django笔记09:一招解决使用regroup模板标签出现的重复分组问题
  7. 阿里云周明:因云而生的基础设施
  8. C语言入门——printf(““)左对齐与右对齐问题
  9. win10右键没有新建选项,无法新建文件夹
  10. 月度计划之2012年10月中旬—11.15