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

问题描述

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

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

解决方案:

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

kotlin 复制代码
open class PersistableData() : Parcelable {
    protected val data = HashMap<String, Any>()

    override fun equals(other: Any?): Boolean {
        if (other == null || other !is PersistableData) return false

        return 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() = 0

    override 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]。
         */
        @JvmStatic
        fun 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 Boolean

    fun putDouble(key: String, value: Double) {
        data[key.validated()] = value
    }

    fun getDouble(key: String) = data[key.validated(checkExists = true)] as Double

    fun putInt(key: String, value: Int) {
        data[key.validated()] = value
    }

    fun getInt(key: String) = data[key.validated(checkExists = true)] as Int

    fun putLong(key: String, value: Long) {
        data[key.validated()] = value
    }

    fun getLong(key: String) = data[key.validated(checkExists = true)] as Long

    fun putString(key: String, value: String) {
        data[key.validated()] = value
    }

    fun getString(key: String) = data[key.validated(checkExists = true)] as String

    fun 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 = 0

        var 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 += 1
            elements = 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

kotlin 复制代码
interface Persistable {
    fun toPersistableData(): PersistableData

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

简单总结一下:

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

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

kotlin 复制代码
@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应用中管理复杂数据结构及其持久化的过程,如果对你有帮助记得点赞收藏!

相关推荐
划破黑暗的第一缕曙光2 小时前
[数据结构]:5.二叉树链式结构的实现1
数据结构
青桔柠薯片2 小时前
数据结构:单向链表,顺序栈和链式栈
数据结构·链表
yangpipi-2 小时前
2. 设计模式之结构型模式
设计模式
XiaoFan0122 小时前
将有向工作流图转为结构树的实现
java·数据结构·决策树
睡一觉就好了。3 小时前
快速排序——霍尔排序,前后指针排序,非递归排序
数据结构·算法·排序算法
齐落山大勇3 小时前
数据结构——单链表
数据结构
皮皮哎哟3 小时前
深入浅出双向链表与Linux内核链表 附数组链表核心区别解析
c语言·数据结构·内核链表·双向链表·循环链表·数组和链表的区别
wWYy.4 小时前
指针与引用区别
数据结构
历程里程碑4 小时前
Linux 17 程序地址空间
linux·运维·服务器·开发语言·数据结构·笔记·排序算法
-dzk-5 小时前
【代码随想录】LC 203.移除链表元素
c语言·数据结构·c++·算法·链表