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 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone1 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui