JetPack—DataStore核心原理与使用
简介
- 首先,DataStore是Jetpack一部分,是一种数据存储解决方案。
- 其次,DataStore使用协程及flow以异步、一致的方式实现数据的存储。
- 最后是DataStore的实现,分为Preferences DataStore和Proto DataStore:
- Preferences DataStore 类似于SharedPreferences,键值对存储,本篇的主要介绍。
- Proto DataStore 将数据作为自定义数据类型的实例进行存储,基于Google protobuf实现。
DataStore和SharedPreferences(SP)
- SP存在的问题:
- 内存浪费问题,加载的数据会一直存在在内存中。
- get方法可能会阻塞主线程。
- apply方法虽然为异步,也可能会发生ANR。
- SP也无法保证类型安全。
- SP不支持跨进程。
- DataStore的特点:
- 基于协程和Flow实现,保证了主线程的安全性。
- 以事务的方式进行处理,保证了操作的原子性、一致性、隔离性及持久性。
- Preferences DataStore可以支持SP的迁移,保证数据的完整性。
- 说明:
- 此处只是列出SP存在的问题,并没有SP一无是处的的说法,DataStore在SP的基础上解决了不少的问题,但是也没有说SP存在的问题已经全部解决了。
- 至少,DataStore在SP的基础上解决了如类型安全、阻塞导致anr等问题,确实这方面优于SP。
使用
Preferences DataStore
基本使用流程
引入
def dataStoreVersion = '1.0.0-beta01'
implementation "androidx.datastore:datastore-preferences:$dataStoreVersion"
创建DataStore
//指定DataStore的文件名
//对应最终件:/data/data/org.geekbang.aac/files/datastore/user_preferences.preferences_pb
private const val USER_PREFERENCES_NAME = "user_preferences"
//扩展属性DataStore,实际类型为DataStore<Preferences>
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME,//指定名称
produceMigrations = {context -> //指定要恢复的sp文件,无需恢复可不写listOf(SharedPreferencesMigration(context, USER_PREFERENCES_NAME))
}
)
定义Key
val SORT_ORDER = stringPreferencesKey("sort_order")
val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
//... 通过查看源码可以看到支持的其它数据类型
存储
//edit要在suspend函数中
override suspend fun updateShowCompleted(showCompleted: Boolean) {dataStore.edit { preferences ->//...这里可以做一些数据的逻辑处理preferences[SHOW_COMPLETED] = showCompleted// 整个tranform中的所有代码块被视为单个事务}
}
读取
override val userPreferencesFlow = dataStore.data.catch { exception ->if (exception is IOException) {//进行IO异常处理,确保能得到默认值Log.e(TAG, "Error reading preferences.", exception)emit(emptyPreferences())} else {throw exception}}.map { preferences ->//真正的获取存储的一个字段val sortOrder = SortOrder.valueOf(preferences[SORT_ORDER] ?: SortOrder.NONE.name)val showCompleted = preferences[SHOW_COMPLETED] ?: falseUserPreferences(showCompleted, sortOrder)}
使用总结
一个对应的preferences_pb文件对应一个.kt文件,里面包含了文件名定义,DataStore定义,Key定义,存取方法定义;例如:
//TaskConfigDataStore.kt
/*** 文件名
*/
private const val TASK_CONFIG_PREFERENCES_FILE_NAME = "task_config_pre"
/*** dataStore对象
*/
val Context.taskConfigDataStore : DataStore<Preferences> by preferencesDataStore(
name = TASK_CONFIG_PREFERENCES_FILE_NAME
)
/** Keys **/
val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
val OPEN_COUNT = intPreferencesKey("open_count")
//other keys
/** 存取方法 **/
fun getShowCompleted(context: Context): Flow<Boolean>
= context.taskConfigDataStore.data.catch { e->if(e is IOException){emptyPreferences()}else{throw e}}.map { pre->pre[SHOW_COMPLETED] ?: false}
suspend fun setShowCompleted(context: Context,showComplete: Boolean){
context.taskConfigDataStore.edit { pre->pre[SHOW_COMPLETED] = showComplete
}
}
// other method
ProtoBuf DataStore
基本使用流程
接入protobuf,以最新的为准 详情信息可参考
protobuf-gradle-plugin
,想详细了解protobuf基础知识,可参考
Protobuf 终极教程
- 在xxx.build中加入:
plugins {//other...id "com.google.protobuf" version "0.8.16"}
- dependencies
// protobuf
def protobufVersion = "3.10.0"
// 3.0.0后Android建议使用javalite
implementation "com.google.protobuf:protobuf-javalite:$protobufVersion"
- 增加protobuf 的块
protobuf {protoc {artifact = "com.google.protobuf:protoc:3.10.0"}
generateProtoTasks {all().each { task ->task.builtins {java {option 'lite'}}}}
}
- 在src/main/目录下建立proto文件,3.8.0以后自动识别此目录下的.proto文件
引入dataStore库
// dataStore
def dataStoreVersion = '1.0.0-beta01'
implementation "androidx.datastore:datastore:$dataStoreVersion"
建立proto文件后,进行rebuild
syntax = "proto3";
option java_package = "org.geekbang.aac";
option java_multiple_files = true;
message UserPreferences {
bool show_completed = 1;
enum SortOrder {UNSPECIFIED = 0;NONE = 1;BY_DEADLINE = 2;BY_PRIORITY = 3;BY_DEADLINE_AND_PRIORITY = 4;
}
SortOrder sort_order = 2;
}
创建Serializer的实现,告诉框架如何读写,这个接口明确规定要有默认值,以便在尚未创建任何文件时使用,这是必要流程,基本是固定写法,用编译器生成的Java类对应api即可
object UserPreferencesSerializer : Serializer<UserPreferences> {override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()@Suppress("BlockingMethodInNonBlockingContext")override suspend fun readFrom(input: InputStream): UserPreferences {try {return UserPreferences.parseFrom(input)} catch (exception: InvalidProtocolBufferException) {throw CorruptionException("Cannot read proto.", exception)}}@Suppress("BlockingMethodInNonBlockingContext")override suspend fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}
定义创建DataStore对象
//老的sp的文件名
private const val USER_PREFERENCES_NAME = "user_preferences"
//新的文件名,对应目录 /data/data/com.codelab.android.datastore/files/datastore/user_prefs.pb
private const val DATA_STORE_FILE_NAME = "user_prefs.pb"
//老的对应的key
private const val SORT_ORDER_KEY = "sort_order"
// Build the DataStore
private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(fileName = DATA_STORE_FILE_NAME,serializer = UserPreferencesSerializer,produceMigrations = { context ->listOf(SharedPreferencesMigration(context,USER_PREFERENCES_NAME) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->// 定义从SharedPreferences到UserPreference的映射if (currentData.sortOrder == SortOrder.UNSPECIFIED) {currentData.toBuilder().setSortOrder(SortOrder.valueOf(sharedPrefs.getString(SORT_ORDER_KEY, SortOrder.NONE.name)!!)).build()} else {currentData}})}
)
存储
//必须是挂起函数,决定其要在协程中使用
suspend fun updateShowCompleted(completed: Boolean) {
//Proto DataStore 提供了一个updateData() 函数,
//用于以事务方式更新存储的对象
//为您提供数据的当前状态,作为数据类型的一个实例,并在原子读-写-修改操作中以事务方式更新数据userPreferencesStore.updateData { currentPreferences ->//当前文件对应的对象currentPreferences.toBuilder().setShowCompleted(completed).build()//对当前对象进行修改}}
读取
val userPreferencesFlow: Flow<UserPreferences> = userPreferencesStore.data.catch { exception ->// dataStore.data throws an IOException when an error is encountered when reading > dataif (exception is IOException) {Log.e(TAG, "Error reading sort order preferences.", exception)emit(UserPreferences.getDefaultInstance())} else {throw exception}}
//单独获取时是阻塞的,在实际使用中建议是异步的,在Kotlin项目中可以使用协程异步实现
suspend fun getUserPreferencesFlowData() = userPreferencesFlow.first()
使用总结
这个是面向相对复杂的对象结构(例如用户信息的本地缓存)的场景下使用,一般以一个proto文件为单位,相关定义,方法做好整体分类即可。
DataStore的优势
- DataStore 是基于 Flow 实现的,所以保证了在主线程的安全性
- 以事务方式处理更新数据,事务有四大特性(原子性、一致性、 隔离性、持久性)
- 没有 apply() 和 commit() 等等数据持久的方法
- 自动完成 SharedPreferences 迁移到 DataStore,保证数据一致性,不会造成数据损坏
- 可以监听到操作成功或者失败结果
相关文章:
JetPack—DataStore核心原理与使用
简介 首先,DataStore是Jetpack一部分,是一种数据存储解决方案。其次,DataStore使用协程及flow以异步、一致的方式实现数据的存储。最后是DataStore的实现,分为Preferences DataStore和Proto DataStore:Preferences Da…...
热烈祝贺|酒事有鲤盛装亮相2023中国(山东)精酿啤酒产业发展创新论坛暨展览会
酒事有鲤(济南)品牌管理有限公司是一家致力于将世界顶级精酿啤酒技术和理念与“ 在地”文化有机融合,做世界认 可的多元化好啤酒,通过精致 舒适的家门口酒馆,让啤酒的 世界观更为完整。 中国生物发酵产业协会联合齐鲁…...
深度强化学习DLR
1 强化学习基础知识 强化学习过程:⾸先环境(Env)会给智能体(Agent)⼀个状态(State),智能体接收到环境给的观测值之后会做出⼀个动作(Action),环境接收到智能体给的动作之后会做出⼀系列的反应,例如对这个动作给予⼀个奖励(Reward…...
Android Handler机制(四) Message源码分析
一. 简介 接上一篇文章:Android Handler机制(三) Looper源码分析 ,我们来继续分析一下Message源码 这一系列文章都是为了深入理解Handler机制. Message 作为消息传递的载体,源码主要分为以下 几个部分: 1. 操作数据相关,类似 getter()和 setter()这种…...
【Git】git命令(全)
Git1、本地操作2、版本管理3、远端仓库4、分支管理5、缓存stash6、遗留rebase7、标签管理8、解决冲突9、参考教程10、示例代码1、本地操作 Linux安装git:yum install git查看git版本 git version查看git设置 git config --list设置git属性 git config --global初始…...
软考论文-成本管理(1)
成本管理 1.成本管理的主要内容? 规划成本:制定一个成本管理的计划。估算成本:根据项目范围说明书,项目管理计划和wbs等文档,采用xxx方法进行估算成本成本预算:可以算工作包的费用,制定预算和…...
Java 多线程 --- 锁的概念和类型划分
Java 多线程 --- 锁的概念和类型划分锁的概念乐观锁与悲观锁公平锁与非公平锁什么是可重入锁独占锁与共享锁轻量级锁和重量级锁自旋锁 (Spinlock)锁的概念 锁可以将多个线程对共享数据的并发访问转换为串行访问, 这样一个共享数据一次只能被一个线程访问, 该线程访问结束后其他…...
python程序员狂飙上头——京海市大嫂单推人做个日历不过分吧?
嗨害大家好鸭!我是小熊猫~ 这个反黑剧其实火了很久了, 但是我现在才有空开始看 该说不说,真的很上头!!! 大嫂简直就像是干枯沙漠里的玫瑰 让人眼前一亮哇~~ 我小熊猫此时此刻就成为大嫂的单推人&…...
浅谈子网掩码、IP地址、网络地址之间关系
文章目录一、什么是子网掩码二、给定IP地址,如何求网络地址网络标识(net-id)和主机标识(host-id)计算步骤三、CIDR地址表示方法(Classless Inter Domain Routing)四、IP地址与MAC地址一、什么是子网掩码 在TCP/IP协议…...
前端优化的解决方案
能缓存的,尽量强缓存。减少HTTP请求数 使用外部引入的css和js文件,并且引入的css和js越少越好使用雪碧图(精灵图)img计算缩放也需要时间,使用base64编码将较小图片嵌入到样式表中,减少请求数因为iframe会阻…...
PYthon组合数据类型的简单使用
Python的数据类型有两种,基本数据类型和组合数据类型,组合数据类型在Python的使用中特别重要。 1.组合数据类型的分类: 2.序列类型 序列类型中元素存在顺序关系,可以存在数值相同但位置不同的元素。序列类型支持成员关系操作符&…...
【Java】P2 基础语法与运算符
Java 基础语法 运算符Java注释方法基本数据类型驼峰命名法Scanner类基本运算除法隐式转换逻辑运算符 以及 短路逻辑运算符三元运算符前言 上一节内容涵盖Java的基础知识,包含安装下载,JDK与JRE等。 链接:https://blog.csdn.net/weixin_43098…...
【并发基础】Java中线程的创建和运行以及相关源码分析
目录 一、线程的创建和运行 1.1 创建和运行线程的三种方法 1.2 三者之间的继承关系 二、Thread类和Runnable接口的区别 2.1 Runnable接口可以实现线程之间资源共享,而Thread类不能 2.2 实现Runnable接口相对于继承Thread类的优点 三、实现 Runnable 接口和实现 Call…...
Spark Shuffle
Shuffle : 集群范围内跨节点、跨进程的数据分发 分布式数据集在集群内的分发,会引入大量的磁盘 I/O 与网络I/O在 DAG 的计算中,Shuffle 环节的执行性能是最差的 , 会消耗所有类型的硬件资源 (CPU、内存、磁盘、网络) Spark 2.0 后,将 Shuff…...
Linux/MacOS 生成双击可执行文件
双击可执行文件包含两种:终端shell脚本 Unix可执行文件 1.终端shell脚本 随意新建一个文件(可使用command键N,前提是有已打开的文件),输入shell格式的测试代码,比如: #! /bin/sh echo “h…...
Ubuntu三种拨号方法
1.宽带拨号(PPPoE) (1)打开连接。关闭以太网连接,打开有线连接设置,取消勾选“自动连接”选项。 (2)配置连接。在终端输入命令sudo pppoeconf,会看到一系列配置信息,包括用户名、密码,配置完成后会有一些提示信息&…...
Vue-router的引入和安装
什么是Vue-Router?Vue路由器是Vue.js的官方路由器,它与Vue.js核心深度集成,使用Vue轻松构建单页应用程序变得轻而易举。功能包括:嵌套路线映射动态路由模块化,基于组件的路由器配置路由参数,查询࿰…...
无线WiFi安全渗透与攻防(四)之kismet的使用
系列文章 无线WiFi安全渗透与攻防(一)之无线安全环境搭建 无线WiFi安全渗透与攻防(二)之打造专属字典 无线WiFi安全渗透与攻防(三)之Windows扫描wifi和破解WiFi密码 kismet 如果要进行无线网络渗透测试,则必须先扫描所有有效的无线接入点。刚好在Kali Linux中&am…...
2023新版PMP考试有哪些变化?
对于2022年很多事情也都在发生,疫情也都没有完全结束,基金会已经开始通知下一场考试了,很多人也会担心新的考纲会不会给自己带来难度,其实这次六月份的考试很多人都内心已经知道了结果,所以这里也详细说一下新考纲的改…...
P8074 [COCI2009-2010#7] SVEMIR 最小生成树
[COCI2009-2010#7] SVEMIR 题目描述 太空帝国要通过建造隧道来联通它的 NNN 个星球。 每个星球用三维坐标 (xi,yi,zi)(x_i,y_i,z_i)(xi,yi,zi) 来表示,而在两个星球 A,BA,BA,B 之间建造隧道的价格为 min{∣xA−xB∣,∣yA−yB∣,∣zA−zB∣}\min\{|x_A-x_…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
