Android笔记(二十):JetPack DataStore 之 Proto DataStore

news2025/7/13 23:45:02

Jetpack DataStore 是一种数据存储解决方案,主要适用于小型数据的处理。它可以通过协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。DataStore有两种实现方式(1)Preferences DataStore 和 (2)Proto DataStore。Proto DataStore将数据作为自定义类型的实例进行存储。

一.了解Protocol Buffers

在了解Protocol Buffers之前需要了解序列化和反序列化。

序列化:就是将代码中生成的对象生成二进制流形式保存起来,也可以将二进制流写入到文件或其他媒介中存储或传输(如网络传输)。
反序列化:就是将二进制流转换成代码可以处理的对象。

Protocol Buffers翻译成协议缓存区,是Google公司推出的一种语言中立、平台中立的数据序列化的方法,类似xml,但是比xml更小、更加灵活,处理更快。
在这里插入图片描述

Protobuffer的工作流程:
首先需要通过在 .proto 文件中定义消息类型来指定数据的结构以及要序列化的服务;
接着根据proto文件可以在不同语言的代码中,将该文件自动生成对应的结构类型(消息的逻辑结构);根据结构类型创建对应的消息(要处理对象)。
然后,编译器使用协议对其进行编译。按照预先定义的架构用于对序列化数据进行编码和解码。解码生成序列化数据,以二进制数据的形式保存起来;也可以对可序列化数据进行编码,在代码中将这些二进制数据转换成代码需要的对象。

二.使用Proto DataStore

Proto Datastore采用Protocol Buffers来定义架构,使得按键存取值可以使用正确的数据类型。Proto DataStore 实现使用 DataStore 和协议缓冲区将类型化对象保留在磁盘上。仍然使用Android笔记(十九)类似的实例,通过示例来了解Proto DataStore的应用。

1.项目模块的build.gradle.kt修改配置

因为构建配置语言采用Kotlin DSL,因此配置的相同选项与谷歌官方网站介绍的Groovy DSL配置有细节不同。

添加协议缓存区插件

plugins{

id(“com.google.protobuf”) version “0.9.4”
}

添加协议缓存区和Proto DataStore依赖项

implementation(“androidx.datastore:datastore-core:1.0.0”)
implementation(“com.google.protobuf:protobuf-javalite:3.19.4”)

配置协议缓存区
在build.gradle.kt中增加如下protebuf的配置

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.23.4"
    }
    // Generates the java Protobuf-lite code for the Protobufs in this project. See
    // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
    // for more information.
    generateProtoTasks {
        all().forEach {task->
            task.builtins {
                id("java"){
                    option("lite")
                }
            }
        }
   }
 }

另外,还需要指定转换文件的源目录,需要在build.gradle.kt中设置如下配置:

android{
...
	sourceSets.named("main"){
        proto{
           srcDir("src/main/proto")
        }
    }
}

2. 创建.proto文件

(1)安装Protocol Buffers插件
为了在Android Studio中更容易编辑.proto文件,可以在Android Studio中安装Protocol Buffers插件,如下图所示:
在这里插入图片描述
(2)创建.proto文件
在.proto文件中定义架构,采用上例的两个属性:账号名name和访问次数counter。
在app/src下创建一个新的目录,命名为proto,然后在新创建的proto目录下定义如下UserPreferences.proto文件:
在这里插入图片描述

UserPerferences.proto文件内容如下:

syntax = "proto3";
//java_package选项:java生成的类所在的包
option java_package = "com.eyes.datastore.utils";
//java_multiple_files选项:如果为 false,则只会.java为此文件生成一个.proto文件,如果true,.java将为每个 Java 类/枚举/等生成单独的文件。
option java_multiple_files = true;

message UserPreferences{
  int32 counter = 1;//第一个关键字对应的取值类型为int32
  string email = 2; //第二个关键字对应的取值类型是string
  optional string name = 3; //可选项,第三个关键字对应的取值类型是string
}

有一个细节需要注意,.proto文件只能在project层次结构下可以查看到该文件。如果采用Android层次结构,则无法在目录层次结构中找到.proto文件。
在这里插入图片描述
(3)构建模块,自动生成对应Java代码
然后,构建该项目模块,可以发现在模块的project项目层次下的模块名-》build-》generated-》source-》proto-》java-》指定包名下会自动生成对应的java代码,如下图所示:
在这里插入图片描述

3.创建序列化器

数据序列化分为两个步骤:

定义一个实现 Serializer 的类,其中 T 是 proto 文件中指定的类型。此序列化器类会告知 DataStore 如何读取和写入数据类型。
创建 DataStore 实例,其中 T 是在 proto 文件中定义的类型。在 Kotlin文件顶层调用该实例一次,便可在应用的所有其余部分通过此属性委托访问该实例。

object UserPreferencesSerializer: Serializer<UserPreferences> {

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

    override suspend fun readFrom(input: InputStream): UserPreferences {
        try {
            return UserPreferences.parseFrom(input)
        } catch (e: InvalidProtocolBufferException) {
            throw CorruptionException("无法读取UserPreferences.proto")
        }
    }

    override suspend fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}

在文件的顶层创建Proto DataStore对象,代码如下:

val Context.userPreferencesDataStore: DataStore<UserPreferences> by dataStore(
    fileName = "UserPreferences.pb",
    serializer = UserPreferencesSerializer
)

上述的代码中:

filename 参数会告知 DataStore 使用哪个文件存储数据
serializer 参数会告知 DataStore 在第 1 步中定义的序列化器类的名称。

4. 实体类UserBean

为了在Kotlin代码中更好地处理数据,这里定义一个实体类UserBean,使得可以将序列化的数据生成对应的UserBean对象。

data class UserBean(val counter:Int,val email:String,var name:String="客人")

这个实体类是可选项,可以根据项目的需求不需要定义。

5. 实用类DataStoreUtils

定义DataStoreUtils实体类,实现对数据的具体的读写操作,代码如下:

class DataStoreUtils(val context: Context) {
    val userFlow: Flow<UserBean> =
        context.userPreferencesDataStore.data.map{userPreferences->
            UserBean(userPreferences.counter,
                     userPreferences.email,
                     userPreferences.name)
    }

    suspend fun updateData(email:String,name:String="无昵称"){
        context.userPreferencesDataStore.updateData {userPreferences->
            userPreferences.toBuilder()
                .setCounter(userPreferences.counter+1)
                .setEmail(email)
                .setName(name)
                .build()
        }
    }
}

6. 定义界面MainScreen

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(dataStoreUtils: DataStoreUtils, modifier:Modifier = Modifier){
    val context = LocalContext.current

    var emailInput by remember{ mutableStateOf("guest@eyes.com") }
    var nameInput by remember{ mutableStateOf("客人") }
    var rememberInput by remember{ mutableStateOf(false) }
    var content by remember{ mutableStateOf("") }

    val scope = rememberCoroutineScope()
    //读取数据
    LaunchedEffect(Unit){
          dataStoreUtils.userFlow.collect{userBean->
              content = "账号:${userBean.email},昵称:${userBean.name}访问次数是:${userBean.counter}"
          }
    }
    Column{
        TextField(modifier = Modifier.fillMaxWidth(),
            value = emailInput,
            onValueChange = {it:String->
                emailInput = it
            },
            label ={Text("输入账号")},
            leadingIcon = {
                Icon(imageVector = Icons.Filled.Email,contentDescription = "账号")
            }
        )
        TextField(modifier = Modifier.fillMaxWidth(),
            value = nameInput,
            onValueChange = {it:String->
                nameInput = it
            },
            label ={Text("输入昵称")},
            leadingIcon = {
                Icon(imageVector = Icons.Filled.Person,contentDescription = "昵称")
            }
        )
        Row{
            Checkbox(checked = rememberInput, onCheckedChange ={
                rememberInput = it
                if(rememberInput)
                    scope.launch {
                        //写入数据
                        dataStoreUtils.updateData(emailInput,nameInput)
                    }
            })
            Text("记住账号")
        }

        if(!emailInput.isBlank())
            Text(text = content ,fontSize=20.sp)

    }
}

7. 定义主活动MainActivity调用主界面MainScreen

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ForCourseTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MainScreen(DataStoreUtils(this))
                }
            }
        }
    }
}

运行结果如下图所示

在这里插入图片描述
通过Device Explorer查看对应的项目模块的文件处理情况,在data->data->模块包名->files->datastore目录下的文件可序列化的数据存储在UserPerferences.pb中,具体文件内容如下图所示:
在这里插入图片描述

参考文献

DataStore
https://developer.android.google.cn/topic/libraries/architecture/datastore?hl=zh-cn

Gradle Kotlin DSL Primer
https://docs.gradle.org/current/userguide/kotlin_dsl.html

customizing-protobuf-compilation
https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation

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

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

相关文章

基于ssm房屋租赁平台的设计与开发论文

摘 要 目前对于在外的人员来说租赁房屋是最基本的问题。对于房屋的租赁可以选择直接找房东、找专业的房屋租赁公司和自己在网上找房屋。自己找房东的问题在于需要时间&#xff0c;而且对于需要提前租赁房屋的需要多次跑到小区&#xff0c;找中介租赁房屋的问题在于费用问题&am…

消除蛋蛋派

欢迎来到程序小院 消除蛋蛋派 玩法&#xff1a;消除游戏&#xff0c;三个相同形状的蛋蛋连成一条直线即可消除&#xff0c;点击鼠标左键移动球球进行消除&#xff0c; 可以使用道具&#xff0c;共有50关卡&#xff0c;快去闯关吧^^。开始游戏https://www.ormcc.com/play/gameS…

Python爬取电影天堂

前言&#xff1a; 本文非常浅显易懂&#xff0c;可以说是零基础也可快速掌握。如有疑问&#xff0c;欢迎留言&#xff0c;笔者会第一时间回复。 一、爬虫的重要性&#xff1a; 如果把互联网比喻成一个蜘蛛网&#xff0c;那么Spider就是在网上爬来爬去的蜘蛛。网络蜘蛛通过网页的…

FFmpeg windows安装与使用

FFmpeg下载&#xff1a; 1、进入ffmpeg官网&#xff0c;点击“Download”。官网地址&#xff1a;FFmpeg 2、选择对应环境的编译工具&#xff0c;如下载windows环境下的ffmpeg编译工具 3、点击下载编译好的ffmpeg工具 FFmpeg使用&#xff1a; 1、将ffmpeg编译的bin文件复制出来…

如何用Excel制作一张能在网上浏览的动态数据报表

前言 如今各类BI产品大行其道&#xff0c;“数据可视化”成为一个热门词汇。相比价格高昂的各种BI软件&#xff0c;用Excel来制作动态报表就更加经济便捷。今天小编就将为大家介绍一下如何使用葡萄城公司的纯前端表格控件——SpreadJS来实现一个Excel动态报表&#xff1a; 实…

uniapp 添加分包页面,配置分包预下载

为什么要分包 ? 分包即将小程序代码分成多个部分打包&#xff0c;可以减少小程序的加载时间&#xff0c;提升用户体验 添加分包页面 比较便捷的方法是使用vscode插件 uni-create-view 新建分包文件夹 以在我的页面&#xff0c;添加分包的设置页面为例&#xff0c;新建文件夹 s…

五、Microsoft群集服务(MSCS)环境的搭建

一、【目的】 学会利用Windows Server布置群集环境。 二、【设备】 FreeNAS11.2&#xff0c;Windows Server 2019 三、【要求】 学会利用Windows Server布置群集环境&#xff0c;掌握处理问题的能力。 配置表&#xff1a; 节点公网IP(public)内网IP(private)群集IP(clust…

RocketMQ系统性学习-RocketMQ高级特性之文件恢复与 CheckPoint 机制

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 【11来了】文章导读地址&#xff1a;点击查看文章导读&#xff01; &#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f3…

理解AI思维链:AI领域的核心概念及其意义

理解AI思维链&#xff1a;AI领域的核心概念及其意义 引言AI思维链的定义AI思维链的重要性实际应用案例分析面临的挑战与未来展望结语 引言 在这个日益由数据驱动的时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为科技领域的一颗耀眼的明星&#xff0c;其影响力遍…

TG5032CGN TCXO / VC-TCXO(超高稳定10pin端子型)

TG5032CGN 晶振是EPSON推出的一款额定频率10MHz至40MHz的石英晶体振荡器&#xff0c;该型号采用互补金属氧化物半导体技术&#xff0c;输出波形稳定可靠。外形尺寸为5.0 3.2 1.45mm具有小尺寸,高稳定性。该款晶体振荡器&#xff0c;可以在G&#xff1a;-40C至 85C的温度内稳定…

达梦到达梦的外部链接dblink(DM-DM DBLINK)

一. 使用场景&#xff1a; 部链接对象&#xff08;LINK&#xff09;是 DM 中的一种特殊的数据库实体对象&#xff0c;它记录了远程数据库的连接和路径信息&#xff0c;用于建立与远程数据的联系。通过多台数据库主库间的相互通讯&#xff0c;用户可以透明地操作远程数据库的数…

v-if与v-show的区别

v-if指令可以控制一个元素的显示和隐藏&#xff0c;那么它是如何实现的&#xff1f;它和看起来很像的v-show指令有什么区别呢&#xff1f; 如果v-if指令的值为假&#xff0c;那么这个元素不会被插入DOM。 下面的代码 <div v-if"true">one</div><div…

海外社媒营销新趋势,品牌出海如何做?

社交媒体在网上的影响力是毋庸置疑的。投资社交媒体平台并建立公司形象&#xff0c;提高产品运营收入&#xff0c;提升品牌知名度&#xff0c;对于吸引对您所提供的产品感兴趣的人至关重要。 然而&#xff0c;社交媒体格局总是在变化&#xff0c;这意味着您需要掌握新的社交媒…

【Jmeter】循环执行某个接口,接口引用的参数变量存在规律变化

变量设置成下面的值即可 ${__V(supplierId_${supplierIdNum})}

【项目问题解决】% sql注入问题

目录 【项目问题解决】% sql注入问题 1.问题描述2.问题原因3.解决思路4.解决方案1.前端限制传入特殊字符2.后端拦截特殊字符-正则表达式3.后端拦截特殊字符-拦截器 5.总结6.参考 文章所属专区 项目问题解决 1.问题描述 在处理接口入参的一些sql注入问题&#xff0c;虽然通过M…

基于SSM的在线学习系统的设计与实现论文

基于SSM的在线学习系统的设计与实现 摘要 随着信息互联网购物的飞速发展&#xff0c;一般企业都去创建属于自己的管理系统。本文介绍了在线学习系统的开发全过程。通过分析企业对于在线学习系统的需求&#xff0c;创建了一个计算机管理在线学习系统的方案。文章介绍了在线学习…

研究生课程 |《数值分析》复习

搭配往年真题册食用最佳。

SpringMVC:整合 SSM 上篇

文章目录 SpringMVC - 03整合 SSM 上篇一、准备工作二、MyBatis 层1. dao 层2. service 层 三、Spring 层四、SpringMVC 层五、执行六、说明 SpringMVC - 03 整合 SSM 上篇 用到的环境&#xff1a; IDEA 2019&#xff08;JDK 1.8&#xff09;MySQL 8.0.31Tomcat 8.5.85Maven…

Linux磁盘空间不足扩展

先在虚拟机Vmware上扩展磁盘空间 后将fdisk 进行分区之后&#xff0c;在/dev/中找不到新分区文件 3.创建物理卷pv时发现找不到/dev/sda3分区&#xff0c;通过ls查看确认在/dev/中没有找到新分区文件 4.解决方法 执行&#xff1a;partprobe 再查看/dev中是否可以看到新分区文件…

linux中playbook的控制语句

本章主要介绍 playbook中的控制语句。 使用 when 判断语句 block-rescue判断 循环语句 一个play中可以包含多个task&#xff0c;如果不想所有的task全部执行&#xff0c;可以设置只有满足某个 条件才执行这个task&#xff0c;不满足条件则不执行此task。本章主要讲解when 和 …