找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4955

积分

0

好友

656

主题
发表于 昨天 11:30 | 查看: 6| 回复: 0

配置Kotlin Any类型的高效序列化方案

数据序列化是每一位移动开发者都无法回避的基础课题。无论是网络请求、文件读写,还是组件间的数据传递,都离不开它。在七年的Android开发经历中,我最近才遇到一个颇为有趣的“坑”。本文将围绕两种序列化方式混合使用引发的问题展开,并揭示其背后关于类加载器工作原理的深层原因。理解之后,你便能实现从SerializableParcelable的渐进式迁移,而无需一次性重写所有类。

Parcelable本身并非通用序列化方案,它无法用于磁盘存储或直接处理网络请求,因此不能完全替代Serializable。但不可否认,在Android体系中,Parcelable的性能优势巨大。这意味着在未来很长一段时间内,我们的项目中必然存在两种方案并存的情况,因此掌握它们的正确共存之道至关重要。

问题起源

这个问题的发现,源于公司日志系统中一个偶现的崩溃。我们先通过一个简化示例来定位问题所在:

data class ScreenViewModel(
    val payload: Any
): Serializable

在这个页面模型中,payload字段可能承载来自深度链接的字符串,也可能是跳转Activity时传递的Intent对象。乍一看似乎没问题?但让我们看看Intent类的声明:

public class Intent implements Parcelable, Cloneable

问题浮现了:与String不同,Intent本身并未实现 Serializable接口!因此,我们需要设计一套兼容逻辑,能够自动识别类型并选择正确的序列化方式,同时保证上层调用代码无需改动。

data class ScreenViewModel(
    val payload: Any
): Parcelable {
    companion object CREATOR : Parcelable.Creator<ScreenViewModel> {
        override fun createFromParcel(parcel: Parcel): ScreenViewModel {
            return ScreenViewModel(parcel)
        }
        override fun newArray(size: Int): Array<ScreenViewModel?> {
            return arrayOfNulls(size)
        }
    }
    constructor(
        parcel: Parcel
    ) : this(
        payload = parcel.readPayload(),
    )
    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writePayload(payload, flags)
    }
    override fun describeContents(): Int = 0
}

在这段Parcelable样板代码中,核心在于两个扩展方法:readPayloadwritePayload。接下来,我们来实现它们。

实现读写逻辑

要实现动态的类型识别,我们只需在写入对象时,附带一个类型标记:

private const val TYPE_NULL = 0
private const val TYPE_PARCELABLE = 1
private const val TYPE_SERIALIZABLE = 2

fun Parcel.writePayload(payload: Any?, flags: Int) {
    when (payload) {
        is Parcelable -> {
            writeInt(TYPE_PARCELABLE)
            writeParcelable(payload, flags)
        }
        is Serializable -> {
            writeInt(TYPE_SERIALIZABLE)
            writeSerializable(payload)
        }
        null -> {
            writeInt(TYPE_NULL)
        }
        else -> {
            throw IllegalArgumentException("Unsupported type for payload")
        }
    }
}

相应地,在读取时根据标记使用对应的方法:

fun Parcel.readPayload(): Any {
    return requireNotNull(readNullablePayload())
}

fun Parcel.readNullablePayload(): Any? {
    return when (readInt()) {
        TYPE_PARCELABLE -> readParcelable<Parcelable>()
        TYPE_SERIALIZABLE -> readSerializable<Serializable>()
        TYPE_NULL -> null
        else -> throw IllegalArgumentException("Unknown payload type")
    }
}

现在,只剩下readParcelablereadSerializable这两个方法需要补全了。看起来只是简单地调用系统API?但这里隐藏着一个大坑。先看一个最容易想到的实现:

inline fun <reified T> Parcel.readParcelable(): T? {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        readParcelable(T::class.java.classLoader, T::class.java)
    } else {
        readParcelable(T::class.java.classLoader)
    }
}

这段代码看似没有问题,但当我们要读取的对象本身就是Parcelable这个抽象类型时(正如我们例子中的payload: Any,其运行时类型是Parcelable),问题就出现了。当我们获取Parcelable::class.java.classLoader时,得到的是java.lang.BootClassLoader——这是虚拟机的系统类加载器,其中只有系统类,不包含你App中的任何自定义类,因此反序列化必然会失败。

解决方案其实很直接:如果我们当前的泛型类型T就是Parcelable本身,那么就不使用它自身的类加载器,转而使用当前应用上下文的类加载器(通常是dalvik.system.PathClassLoader,它包含了App的所有类):

inline fun <reified T> Parcel.readParcelable(): T? {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        if (T::class != Parcelable::class) {
            readParcelable(T::class.java.classLoader, T::class.java)
        } else {
            readParcelable(Thread.currentThread().contextClassLoader, T::class.java)
        }
    } else {
        readParcelable(T::class.java.classLoader)
    }
}

处理Serializable的逻辑与之完全相同:

inline fun <reified T> Parcel.readSerializable(): T? {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        if (T::class != Serializable::class) {
            readSerializable(T::class.java.classLoader, T::class.java)
        } else {
            readSerializable(Thread.currentThread().contextClassLoader, T::class.java)
        }
    } else {
        readSerializable() as T
    }
}

这套序列化读写工具方法不仅适用于Any类型的payload字段,如果你的某个属性不确定是Parcelable还是Serializable,也可以直接使用这些方法。

反过来可以吗?把 Parcelable 嵌套进 Serializable 里

我们上面讨论的是“在Parcelable里存放任意可能是SerializableParcelable的对象”。在我的实际场景中,需要将Intent放入payload,这个方案是目前唯一可行的——毕竟我们无法修改Android源码让Intent实现Serializable。而且,反过来做通常也缺乏实际意义。

但从纯技术角度讲,反向实现是完全可行的:为你的类实现Serializable接口,然后重写序列化与反序列化的逻辑。你甚至可以使用Externalizable接口来实现(虽然Serializable也支持自定义序列化,但Externalizable的写法通常更加清晰和优雅)。

总结

一个有趣的现象是,面试中常被问及的、看似基础的序列化问题,深入探究后竟藏着如此多SDVK未曾明示的细节。解决它甚至需要对系统运行原理有比许多资深开发者更深入的理解。这恰恰说明了移动开发中基础知识的深度与广度。

原文链接https://proandroiddev.com/how-to-configure-kotlin-any-serialization-with-parcelable-and-serializable-in-android-550bd88a499a





上一篇:Python封装从入门到实战:15个代码练习详解属性访问与验证
下一篇:项目管理陷阱:为何简单的需求变更常导致模块重做?
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-7 15:59 , Processed in 0.568276 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表