Android Jetpack组件DataStore之Proto与Preferences存储详解与使用

news2024/11/17 23:42:47

一、介绍

        Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。

如果您当前在使用 SharedPreferences 存储数据,请考虑迁移到 DataStore,Datastore在存储安全和性能都是有保障的。

二、库的介绍与使用

Preferences DataStore 和 Proto DataStore

DataStore 提供两种不同的实现:Preferences DataStore 和 Proto DataStore。

  • Preferences DataStore 使用键存储和访问数据。此实现不需要预定义的架构,也不确保类型安全。
  • Proto DataStore 将数据作为自定义数据类型的实例进行存储。此实现要求您使用协议缓冲区来定义架构,但可以确保类型安全。

为了正确使用 DataStore,请始终谨记以下规则:

  1. 请勿在同一进程中为给定文件创建多个 DataStore 实例,否则会破坏所有 DataStore 功能。如果给定文件在同一进程中有多个有效的 DataStore,DataStore 在读取或更新数据时将抛出 IllegalStateException

  2. DataStore 的通用类型必须不可变。更改 DataStore 中使用的类型会导致 DataStore 提供的所有保证失效,并且可能会造成严重的、难以发现的 bug。强烈建议您使用可保证不可变性、具有简单的 API 且能够高效进行序列化的协议缓冲区。

  3. 切勿在同一个文件中混用 SingleProcessDataStore 和 MultiProcessDataStore。如果您打算从多个进程访问 DataStore,请始终使用 MultiProcessDataStore。

依赖库接入

implementation("androidx.datastore:datastore-preferences:1.0.0") implementation("androidx.datastore:datastore-preferences-core:1.0.0") implementation("androidx.datastore:datastore:1.0.0") implementation("androidx.datastore:datastore-core:1.0.0")

 Preferences DataStore 存储

          使用由 preferencesDataStore 创建的属性委托来创建 Datastore<Preferences> 实例。在您的 Kotlin 文件顶层调用该实例一次,便可在应用的所有其余部分通过此属性访问该实例。这样可以更轻松地将 DataStore 保留为单例。此外,如果您使用的是 RxJava,请使用 RxPreferenceDataStoreBuilder。必需的 name 参数是 Preferences DataStore 的名称

注意:以下代码以kotlin为开发语言

接入流程:

1、初始化DataStore<Preferences>

val Context.dataStore: DataStore<Preferences> by preferencesDataStore("my_datastore")

先定义一个扩展函数,如果不了解扩展函数,可以查看关于kotlin的详解

Kotlin语法详解与实践教程,区分JAVA以及如何闭坑_蜗牛、Z的博客-CSDN博客_kotline

Android kotlin在实战过程问题总结与开发技巧详解_蜗牛、Z的博客-CSDN博客_android kotlin 实战

这样持有全局变量,在其他地方只要传入context就可以拥有datastore的对象,preferences是通过preferencesDataStore存储的。这个会和接下来讲解Proto的存储区分开来

2、保存数据

datastore是通过事物提交,context.dataStore.edit;

edit 源码

 所以我们要在提交的时候把map要保持的数据给封装好

val edit = context.dataStore.edit { map ->
    
    map[keyValue] = (value as T)
}

关于key:

        这里的key不是我们正常的string或者int,需要通过转换一下。

转换的类PreferencesKey已提供了以下key支持:

@JvmName("intKey")
public fun intPreferencesKey(name: String): Preferences.Key<Int> = Preferences.Key(name)


@JvmName("doubleKey")
public fun doublePreferencesKey(name: String): Preferences.Key<Double> = Preferences.Key(name)


@JvmName("stringKey")
public fun stringPreferencesKey(name: String): Preferences.Key<String> = Preferences.Key(name)
@JvmName("booleanKey")
public fun booleanPreferencesKey(name: String): Preferences.Key<Boolean> = Preferences.Key(name)
@JvmName("floatKey")
public fun floatPreferencesKey(name: String): Preferences.Key<Float> = Preferences.Key(name)
@JvmName("longKey")
public fun longPreferencesKey(name: String): Preferences.Key<Long> = Preferences.Key(name)
@JvmName("stringSetKey")
public fun stringSetPreferencesKey(name: String): Preferences.Key<Set<String>> =
    Preferences.Key(name)

key和value的类型要对应,否则在在编译的时候会报错,直接点,value是什么值,key在创建的时候就申请说明类型。

如:

value="zhangsan",那么key就是stringPreferencesKey("key")。

否则报错:

 因为源码在设计的时候,已限定好了:

源码

保存数据

    suspend fun <T> put(context: Context, key: String, value: Any, type: T) {

        val keyValue = getKey(key, type)




        val edit = context.dataStore.edit { map ->

            map[keyValue] = value as T
        }


    }

获取数据

获取数据通过map来获取到flow,通过flow进行流转。

    suspend fun <T> getValue(context: Context, key: String, type: T): Flow<Any?> {
        val keyValue = getKey(key, type)

        val flow = context.dataStore.data.map { map ->

            map[keyValue]
        }

        return flow
    }

如何创建Key?

第一种:为你的每种类型都加一个put个get类型

第二种:如果你觉得每种写法代码臃肿,可以通过泛型来匹配

    private fun <T> getKey(key: String, type: T): Preferences.Key<T> {
        var keyValue: Preferences.Key<T>? = null


        val TypeValue = type.toString()

        if (TypeValue.endsWith(Int::class.java.name)) {
            keyValue = intPreferencesKey(key) as Preferences.Key<T>
        } else if (TypeValue.endsWith(String::class.java.name)) {

            keyValue = stringPreferencesKey(key) as Preferences.Key<T>
        } else if (TypeValue.endsWith(Double::class.java.name)) {

            keyValue = doublePreferencesKey(key) as Preferences.Key<T>
        } else if (TypeValue.endsWith(Float::class.java.name)) {

            keyValue = floatPreferencesKey(key) as Preferences.Key<T>
        } else if (TypeValue.endsWith(Boolean::class.java.name)) {
            keyValue = booleanPreferencesKey(key) as Preferences.Key<T>
        } else if (TypeValue.endsWith(Long::class.java.name)) {

            keyValue = longPreferencesKey(key) as Preferences.Key<T>
        } else if (TypeValue.endsWith(Set::class.java.name)) {

            keyValue = stringSetPreferencesKey(key) as Preferences.Key<T>
        } else {
            throw IllegalAccessException("key type is not support,you need check you key type!!")
        }


        return keyValue
    }

如何调用?

DataStoreConfig.put(
    context!!,
    "name",
    bind.editSave.text.toString(),
    String::class.java
)

bind.editSave.text.toString()是edittext的输入文本。可以直接调用上方的方法

注意:suspend

suspend叫协程,调用类时,需要所调用的地方也要是suspend。

如何处理?

第一:同步

Kotlin 协程提供 runBlocking() 协程构建器,以帮助消除同步与异步代码之间的差异。您可以使用 runBlocking() 从 DataStore 同步读取数据

        runBlocking {
            DataStoreConfig.put(
                context!!,
                "name",
                bind.editSave.text.toString(),
                String::class.java
            )
        }

第二中:异步

异步这边介绍一个框架,kotlinx-coroutines-core

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"

一样,定义一个扩展函数


    val Context.scope: CoroutineScope
        get() = CoroutineScope(EmptyCoroutineContext)
        context!!.scope.launch {
            DataStoreConfig.put(
                context!!,
                "name",
                bind.editSave.text.toString(),
                String::class.java
            )

        }

扩展函数

        kotlin支持对类的函数进行扩展,类似动态新增方法。和定义方法一样,在任何类里面都可以对任何类进行扩展。

扩展写法:类名.扩展对象,方法也是一样

Proto DataStore存储

        Proto DataStore是对象存储,proto并非Java或者kotlin的语言,需要通过第三方插件生成java或者kotlin的对象。该方法和AIDL对象一样。这边先介绍proto对象的使用,如何接入,请看

         Proto 对象促存储和preferences是 DataStore的两种存储方式,一个是对象存储,一个是键值对key-value的存储。

创建 Proto DataStore 来存储类型化对象涉及两个步骤

  1. 定义一个实现 Serializer<T> 的类,其中 T 是 proto 文件中定义的类型。此序列化器类会告知 DataStore 如何读取和写入您的数据类型。请务必为该序列化器添加默认值,以便在尚未创建任何文件时使用。
  2. 使用由 dataStore 创建的属性委托来创建 DataStore<T> 的实例,其中 T 是在 proto 文件中定义的类型。在您的 Kotlin 文件顶层调用该实例一次,便可在应用的所有其余部分通过此属性委托访问该实例。filename 参数会告知 DataStore 使用哪个文件存储数据,而 serializer 参数会告知 DataStore 第 1 步中定义的序列化器类的名称。

Proto文件的创建

先在main文件夹下创建一个proto文件夹,再在proto文件夹新建一个后缀proto的文件

syntax = "proto3";

option java_package = "com.example.wiik.testdemo.proto";
option java_multiple_files = true;
message Settings {
  int32 example_counter = 1;
  string name=2;
}

这里定义两个变量example_counter 和name,然后rebuild项目,生成对象

 

GeneratedMessageV3是proto的语言版本,这样就完成了一个proto的文件生成。

代码接入

1、创建继承Serializer的类。

object SettingsSerializer : Serializer<Settings> {

    override val defaultValue: Settings
        get() = Settings.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): Settings {
        return Settings.parseFrom(input)
    }

    override suspend fun writeTo(t: Settings, output: OutputStream) {

        t.writeTo(output)
    }
}

2、创建dataStore对象


    val Context.proDataStore: DataStore<Settings> by dataStore(
        fileName = "settings.pb",
        serializer = SettingsSerializer
    )

这里依旧是采用扩展,在SettingsSerializer 顶部扩展,需要的可以看:

Android DataStore Proto存储接入流程详解与使用_蜗牛、Z的博客-CSDN博客

存储对象

这里的存储是通过datastore的updatedata来完成

    suspend fun put(context: Context, name: String, count: Int = 100) {
        context.proDataStore.updateData { store ->
            store.toBuilder().setName(name).setExampleCounter(count).build()
        }
    }

获取对象

proto的对象获取与preferences是一样的,都是获取到flow,通过flow来流传

    suspend fun getSetting(context: Context): Flow<Settings> {

        val flow = context.proDataStore.data.map {
            it
        }
        return flow
    }

         scope.launch {
                val set = SettingsSerializer.getSetting(context!!)

                set?.let {
                    set.collect { set ->
                        runOnUiThread {
                            bind.textResultProto.text =
                                "name=${set?.name},age=${set?.exampleCounter}"
                        }
                    }

                }
            }

这些都是在子线程操作,如果需要更新,需要通过UI线程来完成。

SharePreferences转移到Preference存储

        preferences的出现就是要取代SharePreferences,并且已兼容,如果SharePreferences的存储无法转换到preferences,也就意味着SharePreferences数据将会丢失。

在转移这边,datastore同样也支持。

public fun preferencesDataStore(
    name: String,
    corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
    produceMigrations: (Context) -> List<DataMigration<Preferences>> = { listOf() },
    scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
)

        在创建preferencesDataStore的时候,produceMigrations就是需要转移的sharepreference对象,

只要在初始化的时候加进去即可。

同步

    val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
        "my_datastore",
        produceMigrations = { it ->
            listOf(SharedPreferencesMigration(it, "sp_test"))
        })

我们只需要加入我们的sp的name即可,支持合并多个,如果你当前工程下有多个sp,就创建多少个加入到listof()即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/353362.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vscode构建Vue3.0项目(vite,vue-cli)

构建Vue3.0项目构建Vue3.0项目1.使用Vite构建vue项目的方法以及步骤1. 安装vite2. 运行vite vue 项目3.说明2.使用vue-cli构建vue项目的方法以及步骤1.安装全局vue cli —— 脚手架2、VSCode3.报错4.运行构建Vue3.0项目 1.使用Vite构建vue项目的方法以及步骤 1. 安装vite n…

这才是计算机科学_计算机安全

文章目录一、前言1.1身份认证authentication1.2 权限1.3 开发安全二、黑客2.1 NAND镜像2.2 缓冲区溢出2.3 注入三、加密 cryptography3.1 列位移加密3.2 软件加密3.3 密钥交换一、前言 计算机网络中并不是没有人搞破坏的 但是网络无法区分中要执行的是好是坏 计算机安全&#…

设计模式第七讲-外观模式、适配器模式、模板方法模式详解

一. 外观模式 1. 背景 在现实生活中&#xff0c;常常存在办事较复杂的例子&#xff0c;如办房产证或注册一家公司&#xff0c;有时要同多个部门联系&#xff0c;这时要是有一个综合部门能解决一切手续问题就好了。 软件设计也是这样&#xff0c;当一个系统的功能越来越强&…

最大权闭合子图(最小割模型)

1&#xff0c;定义&#xff1a; 1&#xff0c;最大权闭合子图是最小割的一个模型。即每一个子图中的每一个点&#xff0c;其出边的点也全应该在这个子图中。而所有子图中&#xff0c;其点的权值和最大就是最大权闭合子图。 2&#xff0c;构建该图&#xff0c;我们把所有正权值…

Docker镜像创建及管理(Hub官方仓库使用及私有注册中心搭建)

写在前面 系统环境&#xff1a;centos 7 一、Docker如何创建镜像 镜像的来源有两种&#xff1a; 从镜像仓库下载镜像&#xff1b;自己创建新的镜像。创建分为两种&#xff1a;&#xff08;1&#xff09;基于已有镜像创建&#xff1b;&#xff08;2&#xff09;使用Dockerfi…

【数据治理-03】无规矩不成方圆,聊聊如何建立数据标准

无规矩&#xff0c;不成方圆&#xff01;数据标准&#xff08;Data Standards&#xff09;是保障数据的内外部使用和交换的一致性和准确性的规范性约束&#xff0c;作为数据治理的基石&#xff0c;是绕不开的一项工作&#xff0c;如此重要的活如何干&#xff0c;咱们一起聊聊。…

【数据结构】排序算法

目录 1.理解排序 1.1 排序的概念 1.2 排序的运用场景 1.3 常见的排序算法 2.插入排序算法 2.1 直接插入排序 2.2 希尔排序 3.选择排序算法 3.1 直接选择排序 3.2 堆排序 4.交换排序算法 4.1 冒泡排序 4.2 快速排序 4.2.1 hoare 法 4.2.2 挖坑法 4.2.3 前…

前期软件项目评估偏差,如何有效处理?

1、重新评估制定延期计划 需要对项目进行重新评估&#xff0c;将新的评估方案提交项目干系人会议&#xff0c;开会协商一致后按照新的讨论结果制定计划&#xff0c;并实施执行。 软件项目评估偏差 怎么办&#xff1a;重新评估制定延期计划2、申请加资源 如果项目客户要求严格&a…

用股票交易量查询接口是怎么查询a股全天总成交量的?

用股票交易量查询接口是怎么查询a股全天总成交量的&#xff1f;今天下班就以通达信给大家讲解一下&#xff0c;通常是在K线图的底部状态栏&#xff0c;可以在日线进行查看a股成交量。在市场栏底部的子图中。 有当天成交的数量。成交量是表示一定的时间内已经成交的中的成交数量…

【数据挖掘】期末复习笔记(重点知识)

Data Mining 一、概述 1.1 数据挖掘 VS 机器学习 VS 深度学习 VS 知识发现 知识发现&#xff1a; 知识发现就是在数据中发掘知识&#xff0c;将低层次的原始数据转换为高层次的信息。 数据挖掘&#xff1a; 数据挖掘是用一系列的方法或算法从数据中挖掘有用的信息&#xf…

Android中的MVC、MVP、MVVM架构你清楚不?(附实现代码)

01 架构介绍 先来看一下MVC、MVP、MVVM的架构图。 从这些架构图中&#xff0c;可以看到每种架构都有3个模块以及数据流动方向箭头。 模块 在系统架构中&#xff0c;首先要做的就是把系统整体按照一定的原则划分成模块。 数据流动 模块划分之后&#xff0c;模块之间的通信&…

工程监测多通道振弦模拟信号采集仪VTN的MODBUS 通讯协议

工程监测多通道振弦模拟信号采集仪VTN的MODBUS 通讯协议 在 MODBUS 协议下&#xff0c;所有寄存器被定义为“保持寄存器” &#xff08;详见 MODBUS 通讯协议标准说明&#xff09;&#xff0c; 设备支持基于 MODBUS 协议的多个连续寄存器读取、单个寄存器写入两种指令码&#x…

电液伺服阀控制器YY-100

供电电源&#xff1a; 24V DC(18&#xff5e;36V)&#xff1b; 控制输入&#xff1a; -10V&#xff5e;10V DC&#xff1b;最大输出&#xff1a; 70mA &#xff1b;增益 &#xff1a; 调节范围——1&#xff5e;40 mA&#xff08;出厂设置——4 mA&#xff09;&#xff1b; 偏置…

C语言从0到1算法小白训练营——day2

我们学习不仅仅是要把难的学会&#xff0c;也要注重基础&#xff0c;注重内功。 接下来我们继续先从基础知识开始&#xff1a; 1. 字符串字符常量注释 1.1 字符串 如&#xff1a;“abc” ①定义&#xff1a;由双引号引起来的一串字符称为字符串。 ②C语言规定&#xff0c;…

【计算机网络】P1 - 物理层

物理层大纲物理层基本概念数据通信基础两种入网方式传输过程源系统、传输系统与目的系统数据与信号信源、信宿与信道三种通信方式两种传输方式大纲 物理层基本概念 物理层解决如何在传输媒体上&#xff08;同轴电缆&#xff0c;光纤等&#xff09;上传输数据比特流。主要任务为…

detach,主线程终止后子线程会结束吗

此前&#xff0c;我对detach的理解是&#xff0c;当主线程退出后&#xff0c;子线程能够继续存在。实际上&#xff0c;当主线程退出后&#xff0c;子线程也随之结束了。先看一个例子&#xff1a; #include <iostream> #include <thread> #include <unistd.h>…

交叉编译 zlib

交叉编译 zlib 概述 zlib 被设计为一个免费的、通用的、不受法律约束的、即不受任何专利保护的无损数据压缩库&#xff0c;可在几乎任何计算机硬件和操作系统上使用。zlib 数据格式本身可以跨平台移植。与Unix 压缩和 GIF 图像格式中使用的 LZW 压缩方法不同&#xff0c;zlib …

RocketMq使用规范(纯技术和实战建议)

概述&#xff1a; 使用规范主要从&#xff0c;生产、可靠性、和消费为轴线定义使用规范&#xff1b;kafka使用核心&#xff1a;削峰、解耦、向下游并行广播通知&#xff08;无可靠性保证&#xff09;和分布式事务&#xff0c;本规范仅从削峰、解耦、向下游并行广播通知论述&am…

OceanBase 4.0解读:兼顾高效与透明,我们对DDL的设计与思考

关于作者 谢振江&#xff0c;OceanBase 高级技术专家。 2015年加入 OceanBase, 从事存储引擎相关工作&#xff0c;目前在存储-索引与 DDL 组&#xff0c;负责索引&#xff0c;DDL 和 IO 资源调度相关工作。 回顾关系型数据库大规模应用以来的发展&#xff0c;从单机到分布式无…

什么是BOM?与焊盘不匹配,怎么办?

什么是BOM&#xff1f; 简单的理解就是&#xff1a;电子元器件的清单&#xff0c;一个产品由很多零部件组成&#xff0c;包括&#xff1a;电路板、电容、电阻、二三极管、晶振、电感、驱动芯片、单片机、电源芯片、升压降压芯片、LDO芯片、存储芯片、连接器座子、插针、排母、…