当前位置: 首页 > news >正文

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核心原理与使用

简介 首先&#xff0c;DataStore是Jetpack一部分&#xff0c;是一种数据存储解决方案。其次&#xff0c;DataStore使用协程及flow以异步、一致的方式实现数据的存储。最后是DataStore的实现&#xff0c;分为Preferences DataStore和Proto DataStore&#xff1a;Preferences Da…...

热烈祝贺|酒事有鲤盛装亮相2023中国(山东)精酿啤酒产业发展创新论坛暨展览会

酒事有鲤&#xff08;济南&#xff09;品牌管理有限公司是一家致力于将世界顶级精酿啤酒技术和理念与“ 在地”文化有机融合&#xff0c;做世界认 可的多元化好啤酒&#xff0c;通过精致 舒适的家门口酒馆&#xff0c;让啤酒的 世界观更为完整。 中国生物发酵产业协会联合齐鲁…...

深度强化学习DLR

1 强化学习基础知识 强化学习过程&#xff1a;⾸先环境(Env)会给智能体(Agent)⼀个状态(State)&#xff0c;智能体接收到环境给的观测值之后会做出⼀个动作(Action)&#xff0c;环境接收到智能体给的动作之后会做出⼀系列的反应&#xff0c;例如对这个动作给予⼀个奖励(Reward…...

Android Handler机制(四) Message源码分析

一. 简介 接上一篇文章:Android Handler机制(三) Looper源码分析 ,我们来继续分析一下Message源码 这一系列文章都是为了深入理解Handler机制. Message 作为消息传递的载体&#xff0c;源码主要分为以下 几个部分: 1. 操作数据相关&#xff0c;类似 getter()和 setter()这种…...

【Git】git命令(全)

Git1、本地操作2、版本管理3、远端仓库4、分支管理5、缓存stash6、遗留rebase7、标签管理8、解决冲突9、参考教程10、示例代码1、本地操作 Linux安装git&#xff1a;yum install git查看git版本 git version查看git设置 git config --list设置git属性 git config --global初始…...

软考论文-成本管理(1)

成本管理 1.成本管理的主要内容&#xff1f; 规划成本&#xff1a;制定一个成本管理的计划。估算成本&#xff1a;根据项目范围说明书&#xff0c;项目管理计划和wbs等文档&#xff0c;采用xxx方法进行估算成本成本预算&#xff1a;可以算工作包的费用&#xff0c;制定预算和…...

Java 多线程 --- 锁的概念和类型划分

Java 多线程 --- 锁的概念和类型划分锁的概念乐观锁与悲观锁公平锁与非公平锁什么是可重入锁独占锁与共享锁轻量级锁和重量级锁自旋锁 (Spinlock)锁的概念 锁可以将多个线程对共享数据的并发访问转换为串行访问, 这样一个共享数据一次只能被一个线程访问, 该线程访问结束后其他…...

python程序员狂飙上头——京海市大嫂单推人做个日历不过分吧?

嗨害大家好鸭&#xff01;我是小熊猫~ 这个反黑剧其实火了很久了&#xff0c; 但是我现在才有空开始看 该说不说&#xff0c;真的很上头&#xff01;&#xff01;&#xff01; 大嫂简直就像是干枯沙漠里的玫瑰 让人眼前一亮哇~~ 我小熊猫此时此刻就成为大嫂的单推人&…...

浅谈子网掩码、IP地址、网络地址之间关系

文章目录一、什么是子网掩码二、给定IP地址&#xff0c;如何求网络地址网络标识&#xff08;net-id&#xff09;和主机标识&#xff08;host-id&#xff09;计算步骤三、CIDR地址表示方法(Classless Inter Domain Routing)四、IP地址与MAC地址一、什么是子网掩码 在TCP/IP协议…...

前端优化的解决方案

能缓存的&#xff0c;尽量强缓存。减少HTTP请求数 使用外部引入的css和js文件&#xff0c;并且引入的css和js越少越好使用雪碧图&#xff08;精灵图&#xff09;img计算缩放也需要时间&#xff0c;使用base64编码将较小图片嵌入到样式表中&#xff0c;减少请求数因为iframe会阻…...

PYthon组合数据类型的简单使用

Python的数据类型有两种&#xff0c;基本数据类型和组合数据类型&#xff0c;组合数据类型在Python的使用中特别重要。 1.组合数据类型的分类&#xff1a; 2.序列类型 序列类型中元素存在顺序关系&#xff0c;可以存在数值相同但位置不同的元素。序列类型支持成员关系操作符&…...

【Java】P2 基础语法与运算符

Java 基础语法 运算符Java注释方法基本数据类型驼峰命名法Scanner类基本运算除法隐式转换逻辑运算符 以及 短路逻辑运算符三元运算符前言 上一节内容涵盖Java的基础知识&#xff0c;包含安装下载&#xff0c;JDK与JRE等。 链接&#xff1a;https://blog.csdn.net/weixin_43098…...

【并发基础】Java中线程的创建和运行以及相关源码分析

目录 一、线程的创建和运行 1.1 创建和运行线程的三种方法 1.2 三者之间的继承关系 二、Thread类和Runnable接口的区别 2.1 Runnable接口可以实现线程之间资源共享&#xff0c;而Thread类不能 2.2 实现Runnable接口相对于继承Thread类的优点 三、实现 Runnable 接口和实现 Call…...

Spark Shuffle

Shuffle : 集群范围内跨节点、跨进程的数据分发 分布式数据集在集群内的分发&#xff0c;会引入大量的磁盘 I/O 与网络I/O在 DAG 的计算中&#xff0c;Shuffle 环节的执行性能是最差的 , 会消耗所有类型的硬件资源 (CPU、内存、磁盘、网络) Spark 2.0 后&#xff0c;将 Shuff…...

Linux/MacOS 生成双击可执行文件

双击可执行文件包含两种&#xff1a;终端shell脚本 Unix可执行文件 1.终端shell脚本 随意新建一个文件&#xff08;可使用command键N&#xff0c;前提是有已打开的文件&#xff09;&#xff0c;输入shell格式的测试代码&#xff0c;比如&#xff1a; #! /bin/sh echo “h…...

Ubuntu三种拨号方法

1.宽带拨号(PPPoE) (1)打开连接。关闭以太网连接&#xff0c;打开有线连接设置&#xff0c;取消勾选“自动连接”选项。 (2)配置连接。在终端输入命令sudo pppoeconf&#xff0c;会看到一系列配置信息&#xff0c;包括用户名、密码&#xff0c;配置完成后会有一些提示信息&…...

Vue-router的引入和安装

什么是Vue-Router&#xff1f;Vue路由器是Vue.js的官方路由器&#xff0c;它与Vue.js核心深度集成&#xff0c;使用Vue轻松构建单页应用程序变得轻而易举。功能包括&#xff1a;嵌套路线映射动态路由模块化&#xff0c;基于组件的路由器配置路由参数&#xff0c;查询&#xff0…...

无线WiFi安全渗透与攻防(四)之kismet的使用

系列文章 无线WiFi安全渗透与攻防(一)之无线安全环境搭建 无线WiFi安全渗透与攻防(二)之打造专属字典 无线WiFi安全渗透与攻防(三)之Windows扫描wifi和破解WiFi密码 kismet 如果要进行无线网络渗透测试&#xff0c;则必须先扫描所有有效的无线接入点。刚好在Kali Linux中&am…...

2023新版PMP考试有哪些变化?

对于2022年很多事情也都在发生&#xff0c;疫情也都没有完全结束&#xff0c;基金会已经开始通知下一场考试了&#xff0c;很多人也会担心新的考纲会不会给自己带来难度&#xff0c;其实这次六月份的考试很多人都内心已经知道了结果&#xff0c;所以这里也详细说一下新考纲的改…...

P8074 [COCI2009-2010#7] SVEMIR 最小生成树

[COCI2009-2010#7] SVEMIR 题目描述 太空帝国要通过建造隧道来联通它的 NNN 个星球。 每个星球用三维坐标 (xi,yi,zi)(x_i,y_i,z_i)(xi​,yi​,zi​) 来表示&#xff0c;而在两个星球 A,BA,BA,B 之间建造隧道的价格为 min⁡{∣xA−xB∣,∣yA−yB∣,∣zA−zB∣}\min\{|x_A-x_…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道

文/法律实务观察组 在债务重组领域&#xff0c;专业机构的核心价值不仅在于减轻债务数字&#xff0c;更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明&#xff0c;合法债务优化需同步实现三重平衡&#xff1a; 法律刚性&#xff08;债…...

2025年- H71-Lc179--39.组合总和(回溯,组合)--Java版

1.题目描述 2.思路 当前的元素可以重复使用。 &#xff08;1&#xff09;确定回溯算法函数的参数和返回值&#xff08;一般是void类型&#xff09; &#xff08;2&#xff09;因为是用递归实现的&#xff0c;所以我们要确定终止条件 &#xff08;3&#xff09;单层搜索逻辑 二…...

GraphRAG优化新思路-开源的ROGRAG框架

目前的如微软开源的GraphRAG的工作流程都较为复杂&#xff0c;难以孤立地评估各个组件的贡献&#xff0c;传统的检索方法在处理复杂推理任务时可能不够有效&#xff0c;特别是在需要理解实体间关系或多跳知识的情况下。先说结论&#xff0c;看完后感觉这个框架性能上不会比Grap…...

深入理解 C++ 左值右值、std::move 与函数重载中的参数传递

在 C 编程中&#xff0c;左值和右值的概念以及std::move的使用&#xff0c;常常让开发者感到困惑。特别是在函数重载场景下&#xff0c;如何合理利用这些特性来优化代码性能、确保语义正确&#xff0c;更是一个值得深入探讨的话题。 在开始之前&#xff0c;先提出几个问题&…...

华为云Flexus+DeepSeek征文 | 基于Dify构建具备联网搜索能力的知识库问答助手

华为云FlexusDeepSeek征文 | 基于Dify构建具备联网搜索能力的知识库问答助手 一、构建知识库问答助手引言二、构建知识库问答助手环境2.1 基于FlexusX实例的Dify平台2.2 基于MaaS的模型API商用服务 三、构建知识库问答助手实战3.1 配置Dify环境3.2 创建知识库问答助手3.3 使用知…...