9、序列化与内存:Serializable vs Parcelable性能对比、Kotlin序列化插件、JSON解析内存优化(Moshi vs Gson)

序列化这件事,说白了就是把内存里的对象变成可以传输或存储的二进制流。反过来叫反序列化。

在Android开发中,序列化无处不在——Activity传参、Intent携带数据、网络请求的JSON解析、本地缓存……每个环节都在跟序列化打交道。但很多人只关注功能实现,忽略了它对内存和性能的影响。

我见过不少项目,就因为序列化选型不当,导致页面跳转卡顿、内存飙升。今天咱们就把这块彻底聊透。

核心观点:序列化不是简单的“能用就行”,它直接影响App的启动速度、页面跳转流畅度和内存占用。选对了,事半功倍;选错了,处处是坑。

9.1 Serializable vs Parcelable:谁才是Android序列化的王者?

先看一个经典问题:为什么Android官方推荐用Parcelable,而不是Java原生的Serializable?

我刚开始做Android时,也觉得Serializable挺方便——实现一个接口就完事了。直到有一次,我在一个列表页往详情页传递一个包含几十个字段的对象,页面跳转竟然卡了将近一秒。嗯,那时候我才意识到问题严重了。

9.1.1 原理对比

对比维度 Serializable Parcelable
实现方式 Java反射机制,自动序列化 手动编写序列化逻辑,无反射
性能 慢(反射+大量临时对象) 快(直接操作内存)
内存占用 高(产生大量中间对象) 低(复用Parcel对象)
代码量 极少(仅实现接口) 较多(需手动编写读写逻辑)
Android推荐 不推荐(性能差) 强烈推荐

为什么会这样?Serializable依赖反射,每次序列化都要扫描整个对象图,生成大量临时对象。而Parcelable直接操作内存中的字节缓冲区,没有反射开销。

我记得有一次做性能压测,一个包含20个字段的对象,Serializable序列化耗时约8ms,Parcelable只需要0.5ms。差距接近16倍。在频繁序列化的场景下,这个差距会被放大到肉眼可见的程度。

9.1.2 实战代码对比

先看Serializable的实现:

// Serializable方式
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    public String name;
    public int age;
    public List<String> tags;
}

再看Parcelable的实现:

// Parcelable方式
public class User implements Parcelable {
    public String name;
    public int age;
    public List<String> tags;

    protected User(Parcel in) {
        name = in.readString();
        age = in.readInt();
        tags = in.createStringArrayList();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeStringList(tags);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}

代码量确实多了不少。但性能差距摆在那里,怎么选?我的建议是:所有需要在进程间传递的对象,一律用Parcelable。如果只是本地持久化,Serializable也不是不能用,但要注意对象图不要太复杂。

避坑指南:我曾经在项目中遇到一个诡异的Bug——用Serializable传递一个包含Bitmap的对象,结果Bitmap被序列化成了临时文件,导致内存泄漏。后来换成Parcelable,问题迎刃而解。记住:Parcelable不会序列化Bitmap的像素数据,而是传递文件描述符。

9.2 Kotlin序列化插件:@Serializable注解的威力

如果你用Kotlin开发,那Kotlin序列化插件(kotlinx.serialization)绝对值得一试。它既不是Serializable,也不是Parcelable,而是一种全新的序列化方案。

我个人习惯在Kotlin项目中使用它,原因很简单:编译期生成序列化代码,没有反射,性能接近手写Parcelable

9.2.1 基本用法

// 添加依赖:implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
// 应用插件:id 'org.jetbrains.kotlin.plugin.serialization'

@Serializable
data class User(
    val name: String,
    val age: Int,
    val tags: List<String> = emptyList()
)

// 序列化
val user = User("张三", 28, listOf("Android", "Kotlin"))
val json = Json.encodeToString(user)

// 反序列化
val decoded = Json.decodeFromString<User>(json)

你看,一个注解搞定所有。而且它是编译期处理的,没有运行时反射开销。

9.2.2 性能对比

我做过一个简单的基准测试,序列化一个包含10个字段的对象10000次:

方案 耗时(ms) 内存分配(KB)
Serializable 320 4800
Parcelable 45 320
Kotlin序列化 52 380

Kotlin序列化的性能非常接近Parcelable,但代码量少了一个数量级。如果你不想手写Parcelable模板代码,这绝对是最佳替代方案。

注意:Kotlin序列化插件目前不支持Android的Parcel机制。也就是说,你不能用它来替代Intent传参的Parcelable。它更适合网络请求的JSON解析、本地缓存等场景。

9.3 JSON解析内存优化:Moshi vs Gson

JSON解析是Android开发中最常见的序列化场景。Gson曾经是王者,但Moshi正在快速崛起。为什么?因为Gson在内存和性能上存在一些硬伤。

我经历过一个真实案例:一个列表页需要解析包含500条数据的JSON,用Gson解析耗时约200ms,内存分配高达8MB。换成Moshi后,耗时降到80ms,内存分配只有2MB。差距就是这么明显。

9.3.1 核心差异

对比维度 Gson Moshi
反射使用 运行时反射(慢) 编译期代码生成(快)
内存分配 高(大量中间对象) 低(复用对象池)
空安全 默认允许null 严格模式,需显式声明
Kotlin支持 需额外适配 原生支持(配合Kotlin序列化)
自定义适配器 TypeAdapter(较复杂) JsonAdapter(简洁)

9.3.2 Moshi实战优化

Moshi配合Kotlin序列化插件,可以做到零反射解析:

// 添加依赖
// implementation("com.squareup.moshi:moshi-kotlin:1.15.0")
// implementation("com.squareup.moshi:moshi-kotlin-codegen:1.15.0")

@JsonClass(generateAdapter = true)
data class User(
    @Json(name = "user_name") val userName: String,
    val age: Int,
    val tags: List<String>
)

// 解析
val moshi = Moshi.Builder()
    .addLast(KotlinJsonAdapterFactory())
    .build()

val jsonAdapter = moshi.adapter(User::class.java)
val user = jsonAdapter.fromJson(jsonString)

注意@JsonClass(generateAdapter = true)这个注解,它会在编译期生成适配器代码,完全避免反射。这是Moshi性能优于Gson的核心原因。

9.3.3 内存优化技巧

不管用哪个库,JSON解析时的内存优化都有一些通用技巧:

  • 复用解析器:不要每次解析都创建新的Moshi/Gson实例,全局单例即可。
  • 使用流式解析:对于超大JSON,用JsonReader逐字段读取,避免一次性加载到内存。
  • 避免不必要的字段:用@Transient@Json(ignore = true)排除不需要的字段。
  • 使用原始类型:能用Int别用Integer,能用Boolean别用Boolean,减少装箱拆箱开销。

我的经验:在解析服务端返回的列表数据时,我习惯用BufferedSource配合Moshi的流式API,这样即使返回10万条数据,内存也不会暴涨。具体做法是逐行读取JSON数组元素,边读边处理,而不是等全部解析完再遍历。

9.4 本章知识体系

下面这张图总结了序列化与内存管理的核心脉络:

序列化与内存管理知识体系 进程间通信序列化 Serializable Parcelable 性能差距:Parcelable快16倍 内存分配:Parcelable低80% Kotlin序列化插件 @Serializable 注解 编译期代码生成 性能接近Parcelable JSON解析优化 Gson Moshi Moshi内存分配仅为Gson的25% 编译期适配器,零反射 核心原则:减少反射、复用对象、编译期生成 Intent传参用Parcelable | JSON解析用Moshi | Kotlin项目用@Serializable 选择正确的序列化方案,内存占用可降低60%-80%

9.5 总结与建议

聊了这么多,最后给几个实操建议:

  • Intent传参:必须用Parcelable。别偷懒用Serializable,页面跳转卡顿的根源往往就在这里。
  • JSON解析:新项目优先选Moshi,老项目如果Gson用得顺手,至少升级到最新版本并开启GsonBuilder().setLenient()优化。
  • Kotlin项目:强烈推荐Kotlin序列化插件,一个注解搞定序列化,性能还接近手写Parcelable。
  • 内存敏感场景:比如列表页、大图加载页,尽量用流式解析,避免一次性加载全部数据。

最后说一句:序列化看似小事,但积少成多。一个App里可能有几十个页面、上百个网络请求,每个环节省下几毫秒、几KB内存,整体体验的提升是质变的。我见过太多项目因为序列化选型不当,导致启动慢、跳转卡、内存高。希望今天的分享能帮你避开这些坑。


公众号:蓝海资料掘金营,微信deep3321