问题描述
当我编写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应用中管理复杂数据结构及其持久化的过程,如果对你有帮助记得点赞收藏!