**Parcel **是什么?

Parcel是 Android 框架里专门为进程间通信 (IPC) 设计的一种高效序列化容器

在Android开发中,Parcel 是一种用于序列化和反序列化对象的工具。它和普通的Serializable/Parcelable不一样,不适合持久化存储(比如写文件、数据库),而是专门为了Binder 进程通信这种高速传输优化过的。

Parcel最初就是为 Binder IPC 设计的(跨进程传输)。但 Android 发现:既然我有一套高效的序列化机制,那即使在同一进程里 Activity/Fragment/Service 之间,也可以复用这套机制。

所以:

  • IPC 跨进程通信 → Parcel 必须用
  • 同进程 Activity/Service 通信 → 系统也要求Parcelable/Serializable,Parcel 依然被用来当数据容器

在 Android 里推荐Parcelable,因为比Serializable高效。

Parcel 的工作原理

Parcel 的工作方式类似一个结构化的缓冲区(structured buffer),而不是一个普通的字节数组。内部其实就是一块连续的 native 内存 buffer,可以往里写各种基本类型、数组、Parcelable对象。它能将对象的数据打包成一个字节流,以便在进程间通信(IPC)或在组件之间传递。你可以把它想象成一个能打包数据的“快递盒”。📦

Parcel 的工作方式在概念上确实和 Netty 的 Pipeline 有相似之处,它们都依赖于有序处理数据流

它是怎么存的?靠什么区分?

往 Parcel 中写入数据时,例如writeLong()writeInt(),它会将相应的数据类型(如 8 字节的 Long 或 4 字节的 Int)写入到数据流中。

当从 Parcel 中读取数据时,也必须以完全相同的顺序和类型进行读取。系统会根据您调用的方法来决定如何从数据流中读取数据。例如:

  • readLong()会从当前位置读取 8 个字节,并将其解释为一个long类型。
  • readInt()则会读取 4 个字节,并将其解释为一个int类型。

整个过程就像一个协议,但这个协议不是通过特殊的标记字节来区分字段,而是依赖于开发者自己定义的读写顺序

本质上就是一个二进制数据流,靠写入顺序区分,而不是靠字段名。

⚠️没有任何“key”或“字段名”存储,完全靠 写入顺序 + 预定义的数据格式

为什么 Parcel 这么做?

这种设计模式有以下几个优点:

  1. 高效性:Parcel 直接操作二进制数据,避免了像 JSON 那样的字符串解析和序列化过程,因此速度非常快,非常适合在 Android 进程间频繁地传递数据。
  2. 节省空间:它只存储实际的数据,没有额外的字段名或标记,因此占用的内存更少。

使用

定义一个 Parcelable 类

import android.os.Parcel
import android.os.Parcelable
import android.util.Log
 
data class User(val id: Int, val name: String) : Parcelable {
 
    // 序列化:把对象写入 Parcel
    override fun writeToParcel(parcel: Parcel, flags: Int) {
        Log.d("User", "👉 writeToParcel called, id=$id, name=$name")
        parcel.writeInt(id)
        parcel.writeString(name)
    }
 
    override fun describeContents(): Int = 0
 
    // 从 Parcel 里恢复对象
    constructor(parcel: Parcel) : this(
        parcel.readInt(),
        parcel.readString() ?: ""
    ) {
        Log.d("User", "👉 constructor(parcel) called, id=$id, name=$name")
    }
 
    companion object CREATOR : Parcelable.Creator<User> {
        override fun createFromParcel(parcel: Parcel): User {
            Log.d("User", "👉 CREATOR.createFromParcel called")
            return User(parcel)
        }
 
        override fun newArray(size: Int): Array<User?> = arrayOfNulls(size)
    }
}
  • 普通创建(你写的代码)
val model = NadTransitionCardModel(...)

👉 就是普通内存里的对象,不会触发序列化,更不会用到CREATOR

  • 放到Intent/Bundle
intent.putExtra("model", model)

👉 系统会在底层自动调用 序列化writeToParcel()writeObject())。

  • 在另一个 Activity 里取出
val model = intent.getParcelableExtra<NadTransitionCardModel>("model")

👉 系统会调用 反序列化CREATOR.createFromParcel()readObject())恢复对象。

  1. describeContents()
override fun describeContents(): Int = 0
  • 作用:通常返回 0,表示没有特殊内容。
  • 特殊情况:如果对象里有FileDescriptor这种东西,需要返回CONTENTS_FILE_DESCRIPTOR
  • 绝大多数业务对象(你的UserNadTransitionCardModel这种)直接写0就行。

👉 所以这个方法是 必写,但几乎都是固定写法
2. newArray(size: Int)

override fun newArray(size: Int): Array<User?> = arrayOfNulls(size)
  • 作用:当系统需要一次性创建Parcelable数组时调用。
val users: Array<User?> = arrayOfNulls(3)

系统在反序列化一个Parcelable[]的时候会用到它。

  • 你平时单个对象传递用不到,但框架必须要求写。

👉 所以这个也是 必须实现,通常就是arrayOfNulls(size)固定写法。
3. companion object CREATOR

companion object CREATOR : Parcelable.Creator<User> {
    override fun createFromParcel(parcel: Parcel): User {
        Log.d("User", "👉 CREATOR.createFromParcel called")
        return User(parcel)
    }
 
    override fun newArray(size: Int): Array<User?> = arrayOfNulls(size)
}
  • 为什么必须写成静态?
    因为 Android 框架在反射时不会new User()来调你写的createFromParcel,而是直接通过ClassLoader去找 类的静态字段CREATOR
    这是 Parcelable 的硬性约定:系统要求这个字段必须叫CREATOR,必须是public static final,类型必须是Parcelable.Creator<T>

有没有更简单的写法?

有的,就是用 @Parcelize** 注解**。

@Parcelize
data class User(val id: Int, val name: String) : Parcelable

这样编译器会自动帮你生成:

  • writeToParcel
  • describeContents
  • CREATOR
  • newArray

👉 你写起来就只剩一行注解,省掉所有模板代码。

更新: 2025-09-16 17:11:16
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/lpl5lchspf94lcao