Parcelable vs Serializable

前言

Parcelable 和 Serializable 都是实现序列化的接口,那么序列化具体是指什么呢?序列化是指把对象转换为可传输或可存储的状态,反序列化是把序列化后的内容转换为之前的对象。

下面分别讲述两者的设计初衷与具体实现的不同。

Serializable 接口

设计初衷

Serializable 是为了完成对象的持久化,方便保存到本地文件、数据库、网络流等,为对象提供标准的序列化与反序列化操作。

使用

使用 Serializable 来实现序列化非常简单,只需要继承 Serializable 接口:

1
2
3
4
5
6
7
8
9
10
11
public class Person implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

但是经常看到有人实现 Serializable 接口时,还会添加静态变量serialVersionUID,这是 Serializable 对象的版本标识 ID。如果像上面的一样没显示声明的话,默认这个 ID 会根据类的声明和它的成员通过 hash 计算出来。这个版本 ID 会在序列化过程中包含进去,并在反序列化过程中检测,如果本地的serialVersionUID与序列化数据中的不一致,反序列化就会失败并抛出InvalidClassException

序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。

序列化规则

上面介绍了如何实现序列化,那么具体序列化的规则是怎么样的,哪些可以序列化?哪些不会被序列化?

在序列化的过程中,虚拟机会调用对象类中的writeObjectreadObject方法,在用户没有自定义的情况下,默认会调用 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。

所以如果需要序列化对象中有一些数据是敏感的话,如果密码字符串等,可以重写writeObjectreadObject方法,在序列化过程中加密,反序列化过程中解密。

1
2
3
4
5
6
7
8
9
private void writeObject(java.io.ObjectOutputStream out)
throws IOException {
// write 'this' to 'out'...
}
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
// populate the fields of 'this' from the data in 'in'...
}

另外需要注意的是:静态变量和transient关键字标记的变量不会序列化保存。静态变量不是对象的状态,所以不会参与序列化过程。

子类需要实现了 Serializable,父类也要实现 Serializable 接口,不然在默认序列化过程中会报 java.io.NotSerializableException 异常。

Parcelable 接口

设计初衷

Android 设计 Parcelable 的初衷是因为 Serializable 效率过慢,为了在程序内不同组件间以及不同 Android 程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable 是通过 IBinder 通信的消息的载体。

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MyParcelable implements Parcelable {
private int mData;
private Parcelable mParcel;
public int describeContents() {
return 0;
}
// 写数据进行保存
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
out.writeParcelable(mParcel, 0);
}
// 用来创建自定义的 Parcelable 的对象
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
// 读数据进行恢复
private MyParcelable(Parcel in) {
mData = in.readInt();
mParcel = in.readParcelable(Thead.currentThread().getContextClassLoader());
}
}

下面是摘自任玉刚的《Android 开发艺术探索》书中的 Parcelable 的方法说明:

方法 功能 标记位
createFromParcel(Parcel in) 从序列化后的对象中创建原始对象
newArray(int size) 创建指定长度的原始对象数组
MyParcelable(Parcel in) 从序列化后的对象中创建原始对象
writeToParcel(Parcel out, int flags) 将当前对象写入序列化结构中,flags 有两种值 0 和 1。为1是表示当前对象需要作为返回值返回 PARCELABLE_WRITE_RETURN_VALUE
describeContents() 返回当前对象的内容描述。如果含有文件描述符,返回 1,否则返回 0 CONTENTS_FILE_DESCRIPTOR

实现 Parcelable 接口的方式比 Serializable 麻烦一些,不过推荐大家在 Android Studio 中下载 Android Parcelable code generator。 Mac 版的 AS 方法为 Preferences -> Plugins -> Browser repositories… -> 搜索 Parcelable 就可以看到这个插件了。 使用方法为 Command + N 调出 Generate 弹框,选择 Parcelable 即可。

Android 系统本身中的很多类都实现了 Parcelable 接口,比如 Intent、Bundle、Bitmap、ContentValue 等。

对比

编码上实现 Parcelable 接口更麻烦一点。

效率上 Parcelable 的速度比 Serializable 高十倍以上,因为 Serializable 序列化过程使用了反射,需要大量的I/O操作。

所以 Parcelable 主要用在内存序列化上,如 Android 组件之间通信,效率快。而 Serializable 更适合用于数据持久化,如本地保存或网络传输时,Parcelable 在这中情况过程会稍显复杂,且 Android 不同版本 Parcelable 可能不同。

参考文章:

Java 序列化的高级认识

Android Parcelable和Serializable的区别