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

2023-10-29

一、介绍

        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()即可。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Jetpack组件DataStore之Proto与Preferences存储详解与使用 的相关文章

  • 这个方法比 Math.random() 更快吗?

    我是一名初学者 目前已经开始开发一款使用粒子群优化算法的 Android 游戏 我现在正在尝试稍微优化我的代码 并且 for 循环中有相当多的 Math random 几乎一直在运行 所以我正在考虑一种方法来绕过并跳过所有 Math ran
  • 适用于 IOS 和 Android 的支付网关 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在开发一个应用程序 用户必须在澳大利亚餐馆通过应用程序 android ios 付款 有两种付款方式 通过 PayPal 或 Visa
  • 如何在android网络库(ION)中使用自签名SSL?

    使用此网络库 https github com koush ion https github com koush ion 由于当前状态是开发 我想使用自签名 SSL 证书 图书馆论坛有一些讨论 https github com koush
  • 使用 dpi 与 dp 缩放图像之间的差异

    我拥有所有由九个补丁位图组成的 dpi 可绘制目录 xxhdpi 和 xxxhdpi 是否必要 可绘制目录中的可绘制资源文件可检索所有缩放的位图 并且我使用可绘制资源文件 现在 我的问题是我还根据大小 小 正常等 创建了 缩放 布局目录 其
  • 如何在android中压缩和解压png图像

    您好 在我的应用程序中 当我单击 zip 按钮时 我需要压缩图像文件 当我单击解压缩按钮时 我需要解压缩文件 我尝试使用下面的代码来压缩图像 但我的问题是当我单击 zip 按钮时 正在创建 zip 文件 但之后在使用 winzip 软件的系
  • 如何向开发人员发送崩溃报告?

    我开发 Android 应用程序 但在某些情况下我的应用程序force close 如果出现以下情况 我如何向开发人员发送包含详细信息的电子邮件force close随时发生 The ACRA https github com ACRA a
  • 使用 Android Firebase 堆栈推送通知

    我开发了使用 Firebase 接收推送通知的 Android 应用程序 我的代码基于 Firebase Google 官方文档 https firebase google com docs cloud messaging android
  • 使用 Android Studio 进行调试永远停留在“等待调试器”状态

    UPDATE The supposed重复是一个关于陷入 等待调试器 执行时Run 而这个问题就陷入了 等待调试器 执行时Debug 产生问题的步骤不同 解决方案也不同 每当我尝试使用Android Studio的调试功能时 运行状态总是停
  • 如何从android中的外部存储中获取所选文件的文件路径?

    我在选择文件的文件路径时遇到问题 我搜索了整个堆栈溢出 但问题没有解决 从设备中选择文件的代码如下所示 Intent intent new Intent Intent ACTION GET CONTENT intent setType in
  • 适用于 Android 的 Google 云端硬盘\文档 API

    我在几个小时内将 Dropbox 与我的应用程序集成 因为 SDK 描述清晰并且有很好的使用示例 Google Drive 似乎只有一个 一刀切 的 Gdata SDK 它非常重 有很多依赖项 它使我的应用程序的大小增加了三倍 而且不是很直
  • Jetpack 导航:如何从一个嵌套图的子级导航到另一个嵌套图的子级?

    导航结构 MainActivity nav root HomeFragment AuthNestedGraph nav auth BeforeOtpFragment home OtpFragment ProfileNestedGraph n
  • Android 中的列表视图分页

    我有一个列表视图 其中显示了 50 个元素 我决定对视图进行分页 以便视图的每个部分都有 10 个元素 然后单击 下一个 按钮以获取下一个 10 个元素 如何设置10个数据 我关注这篇文章http rakhi577 wordpress co
  • 屏幕开/关检测

    在这里 我试图确定屏幕是否打开 但按下电源锁定 解锁按钮时它似乎不起作用 应用程序运行没有错误 但 if else 中的代码似乎没有效果 Edited现在代码可以工作了 谢谢Olgun 但媒体播放器播放不会停止 并且每次在屏幕上 离屏时都会
  • 是否可以使用 CardView 为浮动操作按钮制作阴影?

    I know CardView不是为此而设计的 但理论上如果cardCornerRadius view size 2它应该导致圆圈 我错过了什么吗 绘制真实的动画阴影并不困难 您可以尝试在 Froyo 等任何 Android 设备上实现 L
  • 如何构建自定义摄像机应用程序?

    我正在尝试开发一个自定义摄像机录像机 当我的设备在 Activity 的 beginRecording 中执行 start MediaRecorder 方法时 应用程序崩溃 我不知道出了什么问题 因为我遵循谷歌API指南 http deve
  • Android Root 执行 su 带参数

    我在使用参数执行 su 时遇到问题 包含空格 我的 Command java 看起来像这样 public class Command Process process public String executeCommand String c
  • Android 地理围栏无法正常工作(未调用 IntentService)

    这是我的代码 安卓清单
  • Android Gradle 同步失败:无法解析配置“:classpath”的所有工件

    错误如下 Caused by org gradle api internal artifacts ivyservice DefaultLenientConfiguration ArtifactResolveException Could n
  • Android AdMob:addView 在返回活动之前不会显示广告

    我正在尝试在游戏顶部添加横幅广告 我的活动使用带有自定义 SurfaceView 的relativelayout 我希望广告与 SurfaceView 重叠 广告会加载并可点击 但不会绘制到屏幕上 当我离开活动并返回时 会绘制广告 例如 通
  • 如何在布局编辑器中模拟沉浸式模式

    我想在布局编辑器中全屏查看我的布局 我正在使用 eclipse 插件 我已经通过选择隐藏了 ActionBar NoActionBar组合中的主题 但导航栏是一个不同的故事 AFAIK 它只能使用代码中的标志来隐藏 我需要在活动 xml 文

随机推荐

  • 蚂蚁森林快捷指令_iPhone 这样偷蚂蚁森林能量,简直就是开挂

    我发现身边有很大一群人 早上要定两个闹钟 一个是偷能量的 另一个是起床的 而常规的偷能量操作无非是 关闹钟 手动打开支付宝 手动进入蚂蚁森林 但这还是略麻烦 很多人在想 速度能不能再快点 不然我的能量要被偷光了 话说我种完一棵树就弃坑了答案
  • 为什么单线程的Redis能这么快?

    1 为什么是单线程 总结 Redis 的普通 KV 存储瓶颈不在 CPU 而往往可能受到内存和网络 I O 的制约 Redis 中有多种类型的数据操作 甚至包括一些事务处理 如果采用多线程 则会被多线程产生的切换问题而困扰 也可能因为加锁导
  • 算法题---合并两个有序数组(乐乐独记)

    1 题意描述 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2 另有两个整数 m 和 n 分别表示 nums1 和 nums2 中的元素数目 请你合并 nums2 到 nums1 中 使合并后的数组同样按 非递减顺序 排列
  • 用AD组策略------控制客户端本地组

    从安全的角度来说是不建议大家把域用户加入到本地Power Users 写这篇文章的目的是告诉大家 可以通过组策略把域用户和域组自动加入到客户端的本地组 实现对客户端本地组的控制 如果善用此策略可以增加系统的安全性 本地Administrat
  • wkwebview 文件服务器,WKWebView 的缓存策略

    缓存策略有以下四种方式 默认的NSURLRequest 缓存策略 后台需要做响应头设置 否则无法进行缓存 存在cache目录 n磁盘紧张会被清除 NSURLCache 和上面类似 可以不需要后台设置也能存储 存在cache目录 n磁盘紧张会
  • Ubuntu建立nfs和tftp环境

    nfs apt安装 sudo apt get install nfs kernel server 编辑配置文件 sudo vi etc exports 在文件末尾加入红框所示内容 其中蓝框内写入nfs工作目录 要传输的文件放在这个目录下 开
  • MATLAB入门教程

    1 MATLAB的基本知识 1 1 基本运算与函数 在MATLAB下进行基本数学运算 只需将运算式直接打入提示号 gt gt 之後 并按入Enter键即可 例如 gt gt 5 2 1 3 0 8 10 25 ans 4 2000 MATL
  • 算法学习——递归

    引言 从这个专栏开始 我们将会一起来学习算法知识 首先我们要一起来学习的算法便是递归 为什么呢 因为这个算法是我很难理解的算法 我希望通过写这些算法博客 来加深自己对于递归算法的理解和运用 当然 学习算法最快的方式便是通过刷题 但是今天这篇
  • jwt 的 token 被获取怎么办

    jwt 签发后 每次请求会续期 如果 token 被抓包后 别人得到后 有没有好的方案解决身份窃取问抗投诉服务器题 签发 token 的时候加入一些验证信息 比如 IP 如果当前 request IP 和签发时候的 IP 不一致就加 bla
  • 1.Python 基本概念

    一 Python 源程序的基本概念 Python源程序就是一个特殊格式的文本文件 可以使用任意文本编辑软件做Python的开发 Python程序的文件扩展名 通常是 py 文件 二 Python 2 x 与 Python 3 x 版本介绍
  • 多线程算法(完整版)

    多线程算法 完整版 算法导论第3版新增第27章 ThomasH Cormen Charles E Leiserson Ronald L Rivest Clifford Stein 邓辉 译 原文 http software intel co
  • 排序(Sort)

    排序 1 排序的基本知识 2 插入类排序 2 1 直接插入排序 2 2 折半插入排序 2 3 希尔排序 3 交换类排序 3 1 冒泡排序 3 2 快速排序 4 选择类排序 4 1 简单选择排序 4 2 堆排序 5 归并排序 6 基数排序 7
  • 基于python开发一个Django博客网站项目

    基于Python和Django框架的简单博客平台 该平台提供了一个用户友好的界面 使用户能够轻松地创建和管理博客文章 评论和标签 前期环境 需要准备的环境 python3以上 创建一个虚拟环境 以兼容不同的Django版本 创建一个文件夹来
  • 图像频谱图-直方图三维可视化 python

    图像频谱图 直方图三维可视化 python代码 目录 1 条纹噪声图像 频谱图3D可视化 2 图像二维直方图3D可视化 1 条纹噪声图像 频谱图3D可视化 频谱图三维可视化思路 将图像经过傅里叶变换 中心化 取log 再3D可视化 代码 i
  • Quick Search —— 快速匹配字符串

    注 正确性有待考察 因为没有题试试水 转载 https blog csdn net superhackerzhang article details 6432559 算法说明 令模式串为p p 0 p 1 p m 1 长度为m 文本串为T
  • ELK-日志服务【kafka-配置使用】

    kafka 01 10 0 0 21 kafka 02 10 0 0 22 kafka 03 10 0 0 23 1 安装zk集群 配置 root es 01 yum y install java maven root es 01 tar
  • Geoserver 重启后引起的事故

    1 geoserver作用来由 geoserver有两种 一种作为单独一个程序来运行 另一种使用geoserver war放到容器中启动使用 Geoserver是用来发布图层 其他的服务使用链接将图层与地图嵌套可以得到想要的数据的直观页面比
  • Android 混淆使用及其字典混淆(Proguard)

    1 使用背景 ProGuard能够通过压缩 优化 混淆 预检等操作 检测并删除未使用的类 字段 方法和属性 分析和优化字节码 使用简短无意义的名称来重命名类 字段和方法 从而使代码更小 更高效 更难进行逆向工程 Android代码混淆 又称
  • 5 款逆向工具,7 款代码分析工具,11 项优化建议

    本文作者 小木箱 原文发布于 小木箱成长营 小木箱成长营 包体积优化系列文章 包体积优化 实战论 怎么做包体积优化 做好能晋升吗 能涨多少钱 包体积优化 方法论 揭开包体积优化神秘面纱 1 引言 Hello 我是小木箱 欢迎来到小木箱成长营
  • Android Jetpack组件DataStore之Proto与Preferences存储详解与使用

    一 介绍 Jetpack DataStore 是一种数据存储解决方案 允许您使用协议缓冲区存储键值对或类型化对象 DataStore 使用 Kotlin 协程和 Flow 以异步 一致的事务方式存储数据 如果您当前在使用 SharedPre