Android随笔-Serializable与Parcelable

一、Serializable(Java 原生序列化)

1.1 原理

Serializable 是 Java 提供的标记接口(Marker Interface),没有任何方法需要实现:

java 复制代码
public interface Serializable {
    // 空接口,仅用于标记该类可被序列化
}

序列化流程

  1. 对象实现 Serializable 接口
  2. ObjectOutputStream 通过反射遍历对象的所有字段(包括父类)
  3. 将字段名、类型、值按固定格式写入字节流
  4. 遇到引用类型字段,递归序列化(自动处理对象图)

关键机制

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
实现方式 实现空接口即可,自动处理 必须手动实现 writeToParcelCREATOR
序列化机制 反射遍历字段,自动读写 手动指定字段读写顺序,直接内存操作
性能 慢(反射 + 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)

相关推荐
天空之城--1 小时前
Android 事件分发机制深度解析——原理、源码与实战综合总结
android
阿pin2 小时前
Android随笔-AIDL
android·开发语言·aidl
2501_916007473 小时前
Bundle ID 注册与管理 App ID 创建指南 命名规则 权限开关与删除注意事项
android·ios·小程序·https·uni-app·iphone·webview
雨白3 小时前
面向对象六大基本原则实战:从零重构支持引擎切换的网络框架
android·架构
张赐荣4 小时前
Android TalkBack 无障碍优化: 为控件自定义转子导航动作
android·microsoft
天空之城--6 小时前
Android Flutter行业动态与学习参考(2026年8月)
android·flutter
quantdash_cc6 小时前
基于 QuantDash 5 分钟 K 线的网格交易策略参数网格搜索寻优实战
android·开发语言·pandas·量化·quantdash
杉氧7 小时前
原生交互:如何在 Flutter 中嵌入 Android/iOS 原生组件并解决手势冲突
android·flutter·dart
Android打工仔8 小时前
Continuation 的链式关系 —— 一个 suspend 函数如何把结果交还给调用者?
android·kotlin