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

轻松解决Android复杂数据结构序列化

问题描述

当我编写quickupload库时,因为需要在 Service中进行上传任务,向Service传递时我发现需要传递的数据很多并且结构复杂,如果处理不好就会导致以下几个问题

  • 耗时: 需要更多时间进行开发和测试以确保正确的数据处理。
  • 容易出错: 由于手动序列化和反序列化逻辑,出现错误的风险更高。
  • 维护: 维护和更新代码的工作量增加,尤其是数据结构发生变化时。

解决方案:

为了解决这个问题,需要编写 PersistableData

open class PersistableData() : Parcelable {protected val data = HashMap<String, Any>()override fun equals(other: Any?): Boolean {if (other == null || other !is PersistableData) return falsereturn data == other.data}override fun hashCode() = data.hashCode()@SuppressLint("ParcelClassLoader")private constructor(parcel: Parcel) : this() {parcel.readBundle()?.let { bundle ->bundle.keySet().forEach { key ->when (val value = bundle[key]) {is Boolean, is Double, is Int, is Long, is String -> data[key] = value}}}}override fun describeContents() = 0override fun writeToParcel(dest: Parcel, flags: Int) {toBundle().writeToParcel(dest, flags)}companion object CREATOR : Parcelable.Creator<PersistableData> {private const val separator = "$"override fun createFromParcel(parcel: Parcel) = PersistableData(parcel)override fun newArray(size: Int): Array<PersistableData?> = arrayOfNulls(size)/*** 从PersistableData JSON表示创建 [PersistableData]。*/@JvmStaticfun fromJson(rawJsonString: String): PersistableData {val json = JSONObject(rawJsonString)val data = PersistableData()json.keys().forEach { key ->when (val value = json.get(key)) {is Boolean, is Double, is Int, is Long, is String -> data.data[key] = value}}return data}}private fun String.validated(checkExists: Boolean = false): String {if (contains(separator))throw IllegalArgumentException("key cannot contain $separator as it's a reserved character, used for nested data")if (checkExists && !data.containsKey(this))throw IllegalArgumentException("no data found for key \"$this\"")return this}fun putBoolean(key: String, value: Boolean) {data[key.validated()] = value}fun getBoolean(key: String) = data[key.validated(checkExists = true)] as Booleanfun putDouble(key: String, value: Double) {data[key.validated()] = value}fun getDouble(key: String) = data[key.validated(checkExists = true)] as Doublefun putInt(key: String, value: Int) {data[key.validated()] = value}fun getInt(key: String) = data[key.validated(checkExists = true)] as Intfun putLong(key: String, value: Long) {data[key.validated()] = value}fun getLong(key: String) = data[key.validated(checkExists = true)] as Longfun putString(key: String, value: String) {data[key.validated()] = value}fun getString(key: String) = data[key.validated(checkExists = true)] as Stringfun putData(key: String, data: PersistableData) {data.data.forEach { (dataKey, value) ->this.data["$key$separator$dataKey"] = value}}fun getData(key: String): PersistableData {val entries = data.entries.filter { it.key.startsWith("$key$separator") }if (entries.isEmpty()) return PersistableData()return PersistableData().also { extractedData ->entries.forEach { (entryKey, entryValue) ->extractedData.data[entryKey.removePrefix("$key$separator")] = entryValue}}}fun putArrayData(key: String, data: List<PersistableData>) {data.forEachIndexed { index, persistableData ->persistableData.data.forEach { (dataKey, value) ->this.data["$key$separator$index$separator$dataKey"] = value}}}fun getArrayData(key: String): List<PersistableData> {val entries = ArrayList(data.entries.filter { it.key.startsWith("$key$separator") })if (entries.isEmpty()) return emptyList()var index = 0var elements = entries.filter { it.key.startsWith("$key$separator$index$separator") }val outList = ArrayList<PersistableData>()while (elements.isNotEmpty()) {outList.add(PersistableData().also { extractedData ->elements.forEach { (entryKey, entryValue) ->extractedData.data[entryKey.removePrefix("$key$separator$index$separator")] =entryValue}entries.removeAll(elements)})index += 1elements = entries.filter { it.key.startsWith("$key$separator$index$separator") }}return outList}/*** 创建一个新的包,其中包含此 [PersistableData] 中存在的所有字段。*/fun toBundle() = Bundle().also { bundle ->data.keys.forEach { key ->when (val value = data[key]) {is Boolean -> bundle.putBoolean(key, value)is Double -> bundle.putDouble(key, value)is Int -> bundle.putInt(key, value)is Long -> bundle.putLong(key, value)is String -> bundle.putString(key, value)}}}/*** 创建一个包含所有字段的JSON字符串表示* 在此 [PersistableData] 中。** 这并不意味着人类可读,而是一种方便的方式来传递复杂的* 使用字符串的结构化数据。*/fun toJson() = JSONObject().also { json ->data.keys.forEach { key ->when (val value = data[key]) {is Boolean, is Double, is Int, is Long, is String -> json.put(key, value)}}}.toString()
}

简单总结一下:

  • 存储各种类型的键值对(Boolean、Double、Int、Long、String)。
  • 提供放入和获取数据的方法(putBoolean、getBoolean等)。
  • 使用带有分隔符的键支持嵌套和数组数据结构。
    转换数据为Bundle和JSON格式,便于存储和检索。
  • 实现Parcelable接口,允许在Android组件之间传递数据。
为了在使用时保持统一性

编写接口 Persistable

interface Persistable {fun toPersistableData(): PersistableDatainterface Creator<T> {fun createFromPersistableData(data: PersistableData): T}
}

简单总结一下:

  • 定义了一个可以转换为PersistableData对象的契约。
  • 确保数据对象的序列化和反序列化方式一致。

当需要对某个数据类进行序列化时只需要实现接口 Persistable,例如 UploadFile数据类

@Parcelize
data class UploadFile @JvmOverloads constructor(val path: String,val properties: LinkedHashMap<String, String> = LinkedHashMap()
) : Parcelable, Persistable {companion object : Persistable.Creator<UploadFile> {private object CodingKeys {const val path = "path"const val properties = "props"}override fun createFromPersistableData(data: PersistableData) = UploadFile(path = data.getString(CodingKeys.path),properties = LinkedHashMap<String, String>().apply {val bundle = data.getData(CodingKeys.properties).toBundle()bundle.keySet().forEach { propKey ->put(propKey, bundle.getString(propKey)!!)}})}override fun toPersistableData() = PersistableData().apply {putString(CodingKeys.path, path)putData(CodingKeys.properties, PersistableData().apply {properties.entries.forEach { (propKey, propVal) ->putString(propKey, propVal)}})}
}

简单总结一下:

  • 使用PersistableData存储其属性,使得状态的保存和恢复变得简单。
  • 包含一个自定义Creator,用于从PersistableData创建UploadFile实例。

总结

我认为这样做 可以简化和优化在Android应用中管理复杂数据结构及其持久化的过程,如果对你有帮助记得点赞收藏!

相关文章:

轻松解决Android复杂数据结构序列化

问题描述 当我编写quickupload库时&#xff0c;因为需要在 Service中进行上传任务&#xff0c;向Service传递时我发现需要传递的数据很多并且结构复杂&#xff0c;如果处理不好就会导致以下几个问题 耗时: 需要更多时间进行开发和测试以确保正确的数据处理。容易出错: 由于手…...

解析PDF文件中的图片为文本

解析PDF文件中的图片为文本 1 介绍 解析PDF文件中的图片&#xff0c;由两种思路&#xff0c;一种是自己读取PDF文件中的图片&#xff0c;然后用OCR解析&#xff0c;例如&#xff1a;使用PyMuPDF读取pdf文件&#xff0c;再用PaddleOCR或者Tesseract-OCR识别文字。另一种使用第…...

微信小程序表单

在我们的课程中&#xff0c;我们深入探讨了微信小程序表单的开发和应用。以下是我们课程的主要内容和收获&#xff1a; 一、课程目标 本课程旨在帮助学生掌握微信小程序表单的基本概念、开发流程和最佳实践。学生将学习如何创建和配置表单组件&#xff0c;处理表单数据&#xf…...

Javascript高级程序设计(第四版)--学习记录

var关键字&#xff1a;定义变量同时可以进行赋值 var message"hello" message 10 可以改变保存的值&#xff0c;也可以改变值的类型&#xff0c;但是不推荐这样写。 var声明的变量会成为包含它的函数的局部变量。 function test(){ var message "hello";…...

DVWA-CSRF-samesite分析

拿DVWA的CSRF为例子 接DVWA的分析&#xff0c;发现其实Impossible的PHPSESSID是设置的samesite1. 参数的意思参考Set-Cookie SameSite:控制 cookie 是否随跨站请求一起发送&#xff0c;这样可以在一定程度上防范跨站请求伪造攻击&#xff08;CSRF&#xff09;。 下面用DVWA CS…...

代码随想录训练营Day48

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、买卖股票的最佳时机4二、买卖股票的最佳时机含冷冻期三、买卖股票含手续费 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 今天是…...

React进阶(五):导航守卫_renderroutes

在《React进阶&#xff08;四&#xff09;&#xff1a;路由介绍》博文中&#xff0c;介绍了React路由相关知识&#xff0c;在实际项目开发过程中&#xff0c;路由之间的跳转必定涉及权限、用户是否登陆等限定条件的判定&#xff0c;故需要导航守卫来完成这一事项。 在实现reac…...

Python基础系列教程:从零开始学习Python

Python有很多功能强大的机器学习和大数据分析包&#xff0c;适合对大数据和人工智能感兴趣的同学学习。要想了解一门语言&#xff0c;首先需要了解它的语法。本文将介绍Python的一些基础语法&#xff0c;包括数据类型、变量类型、条件控制、循环结构等内容。废话少说&#xff0…...

deepl翻译的PDF文档保护密码解除

1、首先将后缀名(.docx)修改为压缩包格式(.zip)。 2、修改解密word加密.py里zip的位置&#xff0c;和新生成的zip的位置和名称 import zipfile import xml.etree.ElementTree as ET import os import shutil# 定义文件路径 zip_file_path rC:\Users\Administrator\Desktop\新…...

LeetCode 算法:二叉树的直径 c++

原题链接&#x1f517;&#xff1a;二叉树的直径 难度&#xff1a;简单⭐️ 题目 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由…...

盘立方期货Kdj幅图指标公式源码

盘立方期货Kdj幅图指标公式源码&#xff1a; N:250; WR1:100-100*(HHV(HIGH,N)-CLOSE)/(HHV(HIGH,N)-LLV(LOW,N)),DOT,COLORLIGHTGREEN; EW:EMA(WR1,5); STICKLINE(WR1<20,WR1,20,1,0),COLORYELLOW; STICKLINE(WR1>80,WR1,80,1,0),COLORYELLOW; RSV:(CLOSE-LLV(LOW…...

SkyWalking 极简入门

1. 概述 1.1 概念 SkyWalking 是什么&#xff1f; FROM Apache SkyWalking 分布式系统的应用程序性能监视工具&#xff0c;专为微服务、云原生架构和基于容器&#xff08;Docker、K8s、Mesos&#xff09;架构而设计。 提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体…...

本篇内容:ArkTS开发系列之事件(2.8.1触屏、键鼠、焦点事件)

上篇回顾&#xff1a; ArkTS开发系列之导航 (2.7动画&#xff09; 本篇内容&#xff1a;ArkTS开发系列之事件&#xff08;2.8.1触屏、键鼠、焦点事件&#xff09; 一、知识储备 1. 触屏事件&#xff1a;包括点击事件、拖拽事件、触摸事件。 点击事件 Button()....onClick(…...

测试的基础知识大全【测试概念、分类、模型、流程、测试用例书写、用例设计、Bug、基础功能测试实战】

测试基础笔记 Day01阶段⽬标⼀、测试介绍⼆、测试常⽤分类2.1 阶段划分单元测试集成测试系统测试验收测试 2.2 代码可⻅度划分⿊盒测试&#xff1a;主要针对功能&#xff08;阶段划分->系统测试&#xff09;灰盒测试&#xff1a;针对接⼝测试&#xff08;阶段划分->集成测…...

Power Apps

目录 一、引言1、Power Apps2、应用场景3、Power Apps的优势与前景4、补充 二、数据源介绍1、SharePoint2、Excel3、Dataverse4、SQL5、补充&#xff08;1&#xff09;OneDrive 三、Power Apps应用类型1、画布应用2、模型驱动应用3、网站 Power Pages 四、Power Automate五、Po…...

qt图像处理-将OpenCV的cv::Mat类型转换为QImage类型

在使用Qt进行图像处理时,经常需要将OpenCV的cv::Mat类型转换为QImage类型。以下是几种有效的方法,可以根据具体情况选择合适的方法进行转换。 方法一:直接使用QImage构造函数 这种方法直接使用QImage的构造函数,通过传递cv::Mat的指针和相关参数来创建QImage对象。这种方…...

代码随想录训练营第十八天 530二叉搜索树的最小绝对差 501二叉搜索树中的众数 236二叉树的最近公共祖先

第一题&#xff1a; 原题链接&#xff1a;530. 二叉搜索树的最小绝对差 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 使用中序遍历的方式&#xff1a;左中右。 定义一个pre节点来存放当前节点的前一个节点。 在中序的时候处理递归逻辑&#xff1a; 首先先向…...

微信小程序之横向列表展示

效果图 参考微信小程序可看 代码&#xff1a; <view class"lbtClass"><view class"swiper-container"><scroll-view class"swiper" scroll-x"true" :scroll-left"scrollLeft"><block v-for"(six…...

无人机巡检小羊仿真

详细视频地址 仿真效果 可视化三维仿真 gazebo物理仿真 px4 飞控仿真 仿qgc简易地面站 详细视频地址...

springboot redission 分布式锁

Spring Boot中使用Redisson实现分布式锁的方法如下&#xff1a; 1. 首先&#xff0c;需要在项目中引入Redisson依赖。在pom.xml文件中添加以下依赖&#xff1a; xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson<…...

Vuex中的重要核心属性

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 Vuex 的核心属性包括&#xff1a; State: State 是 Vuex 存储数据的地方&#xff0c;类似于组件中的 data。它…...

Redis哨兵集群搭建

一、安装Redis 1.安装依赖 yum install -y gcc tcl2.将Redis压缩包解压到对应的目录 tar -zxvf redis-2.8.0.tar.gz mv redis-2.8.0 /usr/local3.编译 cd /usr/local/redis-2.8.0 make && make install4.配置redis.conf # 任意ip都可以访问 bind 0.0.0.0 # 关闭保…...

网络爬虫requests库使用指南

目录 引言 安装requests库 基本用法 发送GET请求 发送POST请求 处理请求头和Cookies 设置请求头 使用Cookies 会话管理 异常处理 流式上传和下载 结语 引言 在Python中进行HTTP请求时&#xff0c;requests库是一个强大且易于使用的第三方库。它允许你发送各种HTTP请…...

VSCODE 配置C++ 与OPENCV

主要是两个json设置 c_cpp_properties.json {"configurations": [{"name": "Win32","includePath": ["${workspaceFolder}/**"],"defines": ["_DEBUG","UNICODE","_UNICODE"],&qu…...

C语言小例程28/100

题目&#xff1a;利用递归方法求5!。 程序分析&#xff1a;递归公式&#xff1a;fnfn_1*4! #include <stdio.h>int main() {int i;int fact(int);for(i0;i<6;i){printf("%d!%d\n",i,fact(i));} } int fact(int j) {int sum;if(j0){sum1;} else {sumj*fac…...

WPF文本绑定显示格式StringFormat设置-特殊格式时间日期和多数据绑定

WPF文本绑定显示格式StringFormat设置 特殊格式设置日期/时间使用系统默认样式自定义格式&#xff1a; 绑定多个属性&#xff08;多重绑定&#xff09;多重绑定中的特殊字符示例&#xff1a; 特殊格式设置 在Textblock等文本控件中&#xff0c;我们经常要显示一些日期和时间&a…...

Java包介绍

今天看jdk文档&#xff0c;顺便写一下java几个包的作用。 java.applet 主要用于创建java applet小应用程序&#xff0c;可以嵌入到网页中能够呈现出特殊的效果&#xff0c;现在基本已经被废弃&#xff0c;很少使用。 java.awt AWT 是Abstract Window ToolKit (抽象窗口工具包…...

【2024.6.21】今日科技时事:科技前沿大事件

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…...

LeetCode:经典题之1491、896 题解与延伸

系列目录 88.合并两个有序数组 52.螺旋数组 567.字符串的排列 643.子数组最大平均数 150.逆波兰表达式 61.旋转链表 160.相交链表 83.删除排序链表中的重复元素 389.找不同 1491.去掉最低工资和最高工资后的工资平均值 896.单调序列 206.反转链表 92.反转链表II 141.环形链表 …...

2024三掌柜赠书活动第二十五期:Rust 游戏开发实战

目录 目录 前言 Rust语言概念 关于《Rust 游戏开发实战》 Rust系统编程的核心点 Rust开发的关键技术和工具 内容简介 作者简介 书中前言/序言 内容介绍 《Rust 游戏开发实战》全书速览 图书目录 结束语 前言 技术圈最近的编程语言新秀当属Rust莫属&#xff0c;Rus…...