一、Serializable(Java 原生序列化)
1.1 原理
Serializable 是 Java 提供的标记接口(Marker Interface),没有任何方法需要实现:
java
public interface Serializable {
// 空接口,仅用于标记该类可被序列化
}
序列化流程:
- 对象实现 Serializable 接口
- ObjectOutputStream 通过反射遍历对象的所有字段(包括父类)
- 将字段名、类型、值按固定格式写入字节流
- 遇到引用类型字段,递归序列化(自动处理对象图)
关键机制:
java
public class User implements Serializable {
private static final long serialVersionUID = 1L; // 版本控制标识
private String name;
private transient String password; // transient 标记的字段不参与序列化
private static int count; // static 字段不参与序列化
// 可选:自定义序列化逻辑
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 先写默认字段
out.writeObject(encrypt(password)); // 自定义处理敏感字段
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.password = decrypt((String) in.readObject());
}
}
底层实现:
- 使用 ObjectStreamClass 描述类的结构
- 通过反射获取 Field\[\],逐个 field.get() / field.set()
- 使用 DataOutputStream 写入基本类型,ObjectOutputStream 处理对象引用
二、Parcelable(Android 专用)
2.1 原理
Parcelable 是 Android 设计的高性能序列化接口 ,专为 IPC(进程间通信)优化:
java
public interface Parcelable {
int describeContents(); // 描述内容类型(通常返回 0)
void writeToParcel(Parcel dest, int flags); // 将对象写入 Parcel
// 内部接口:从 Parcel 中创建对象
interface Creator<T> {
T createFromParcel(Parcel source);
T[] newArray(int size);
}
// Android 13+ 新增:更高效的创建方式
interface ClassLoaderCreator<T> extends Creator<T> {
T createFromParcel(Parcel source, ClassLoader loader);
}
}
序列化流程:
java
public class Book implements Parcelable {
private int id;
private String name;
// 1. 构造器:从 Parcel 中读取数据
protected Book(Parcel in) {
id = in.readInt();
name = in.readString();
}
// 2. 写入 Parcel(序列化)
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
// 3. 描述内容(含有文件描述符时返回 CONTENTS_FILE_DESCRIPTOR)
@Override
public int describeContents() {
return 0;
}
// 4. CREATOR:反序列化的工厂
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in); // 调用构造器
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
}
底层实现:
- Parcel 是一块连续的内存块(Native 层 Parcel.cpp 管理)
- 写入时直接进行内存拷贝(memcpy),没有反射开销
- 跨进程时通过 Binder 驱动进行一次内存映射拷贝
三、核心区别对比
| 对比项 | Serializable |
Parcelable |
|---|---|---|
| 来源 | Java 标准(java.io) |
Android 专用(android.os) |
| 实现方式 | 实现空接口即可,自动处理 | 必须手动实现 writeToParcel 和 CREATOR |
| 序列化机制 | 反射遍历字段,自动读写 | 手动指定字段读写顺序,直接内存操作 |
| 性能 | 慢(反射 + IO 流) | 快(直接内存拷贝,无反射) |
| 序列化体积 | 大(包含类元数据、字段名等) | 小(仅数据本身) |
| 使用场景 | 文件持久化、网络传输、进程内缓存 | Android IPC(Intent、Bundle、Binder、AIDL) |
| 跨进程能力 | ✅ 可以,但效率低 | ✅ 专为跨进程设计 |
| 代码复杂度 | 极低(一行 implements) |
较高(需手写读写逻辑) |
| 版本兼容 | serialVersionUID 控制 |
无内置版本机制,字段增删需手动兼容 |
| 线程安全 | 非线程安全 | Parcel 非线程安全,但 Binder 通信是同步的 |
| 安全性 | ⚠️ 反序列化漏洞风险高 | ✅ 相对安全(无反射调用任意类) |
四、性能差异的具体原因
4.1 Serializable 慢在哪里
java
// 伪代码展示 Serializable 的反射开销
void writeObject(Object obj) {
ObjectStreamClass desc = ObjectStreamClass.lookup(obj.getClass());
// 1. 反射获取类结构
Field[] fields = desc.getFields();
for (Field field : fields) {
// 2. 反射获取字段值
Object value = field.get(obj);
// 3. 判断类型,分支写入
if (field.getType() == int.class) {
writeInt((Integer) value);
} else if (field.getType() == String.class) {
writeUTF((String) value);
}
// ... 更多类型判断
}
}
开销点:
- 反射获取 Field 对象
- 反射 field.get() / field.set() 突破访问控制
- 写入类名、字段名等元数据
- 处理对象图(循环引用检测)
4.2 Parcelable 快在哪里
cpp
// Native 层 Parcel.cpp 的写入逻辑(简化)
status_t Parcel::writeInt32(int32_t val) {
// 直接内存写入,无反射
*reinterpret_cast<int32_t*>(mData + mDataPos) = val;
mDataPos += sizeof(int32_t);
return NO_ERROR;
}
优势点:
- 直接内存操作(memcpy)
- 无反射,无元数据写入
- 内存连续,CPU 缓存友好
- 跨进程时 Binder 驱动只需一次内存拷贝
五、使用场景与选择
5.1 必须使用 Parcelable 的场景
java
// 1. Intent 传参
Intent intent = new Intent();
intent.putExtra("book", book); // book 必须是 Parcelable
// 2. Bundle 传参
Bundle bundle = new Bundle();
bundle.putParcelable("book", book);
// 3. AIDL 接口参数
interface IBookManager {
void addBook(in Book book); // Book 必须是 Parcelable
}
// 4. Service onBind 返回
public IBinder onBind(Intent intent) {
return new BookService.Stub(); // Stub 继承 Binder,内部用 Parcel
}
5.2 适合使用 Serializable 的场景
java
// 1. 数据持久化到本地文件
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("cache/user.ser"))) {
oos.writeObject(user);
}
// 2. 网络传输(但通常用 JSON 更好)
// 3. 进程内缓存(内存中存对象即可,无需序列化)
// 4. 遗留系统兼容
六、Parcelable 的注意事项
6.1 字段顺序必须一致
java
// 写入顺序
dest.writeInt(id); // 先写 int
dest.writeString(name); // 再写 String
// 读取顺序必须严格一致!
id = in.readInt(); // 先读 int
name = in.readString(); // 再读 String
顺序不一致会导致数据错乱或崩溃!
6.2 含可变集合时的深拷贝问题
java
public class Order implements Parcelable {
private List<Item> items;
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(items); // 会递归调用 Item.writeToParcel()
}
}
6.3 Android Studio 自动生成
可以使用插件或 IDE 功能自动生成 Parcelable 代码,或使用 Kotlin 的 @Parcelize:
kotlin
// Kotlin 简化写法(需添加 kotlin-parcelize 插件)
@Parcelize
data class Book(val id: Int, val name: String) : Parcelable
编译器会自动生成 writeToParcel、CREATOR 等样板代码。
七、两者的联系
| 联系点 | 说明 |
|---|---|
| 本质相同 | 都是将对象转换为字节流的序列化机制 |
| 目的相同 | 实现对象的存储或传输 |
| 互补关系 | Serializable 负责 Java 生态通用场景;Parcelable 负责 Android IPC 高性能场景 |
| 可以共存 | 一个类可以同时实现两个接口(虽然不常见) |
八、总结
Serializable 是 Java 的"傻瓜相机"------反射自动处理,简单但慢;Parcelable 是 Android 的"专业单反"------手动调参,繁琐但极快。Android 跨进程通信必须用 Parcelable,其他场景优先 Serializable(或 JSON)