**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 这么做?
这种设计模式有以下几个优点:
- 高效性:Parcel 直接操作二进制数据,避免了像 JSON 那样的字符串解析和序列化过程,因此速度非常快,非常适合在 Android 进程间频繁地传递数据。
- 节省空间:它只存储实际的数据,没有额外的字段名或标记,因此占用的内存更少。
使用
定义一个 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())恢复对象。
describeContents()
override fun describeContents(): Int = 0- 作用:通常返回 0,表示没有特殊内容。
- 特殊情况:如果对象里有
FileDescriptor这种东西,需要返回CONTENTS_FILE_DESCRIPTOR。 - 绝大多数业务对象(你的
User、NadTransitionCardModel这种)直接写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这样编译器会自动帮你生成:
writeToParceldescribeContentsCREATORnewArray
👉 你写起来就只剩一行注解,省掉所有模板代码。
更新: 2025-09-16 17:11:16
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/lpl5lchspf94lcao