序列化(Serialization):将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
二进制序列化保持类型保真度,这对于在应用程序的不同调用之间保留对象的状态很有用。例如,通过将对象序列化到剪贴板,可在不同的应用程序之间共享对象。您可以将对象序列化到流、磁盘、内存和网络等等。远程处理使用序列化“通过值”在计算机或应用程序域之间传递对象。
简单地说,
“序列化”就是将运行时的对象状态转换成二进制,然后保存到流、内存或者通过网络传输给其他端。
Android中序列化有两种方式:Serializable、Parcelable。
Serializable(JAVA自带)
Serializable 是 Java 提供的序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。
使用:
使目标对象实现 Serializable 接口,并声明一个 serialVersionUID 即可。
(不声明serialVersionUID也可以实现序列化,但会对反序列化过程产生影响,后面做介绍。)
private static final long serialVersionUID = 8711368828010083044L;
示例:
以序列化到文件为例,借助 ObjectOutputStream 和 ObjectInputStream:
// 序列化过程
User user = new User(0, "Tom", true); // (id, name, isMale)
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
// 反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
// user 与 newUser 内容完全一样,但并不是同一个对象
User newUser = (User) in.readObject();
in.close();
serialVersionUID:
serialVersionUID 是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID 只有和当前类的 serialVersionUID 相同才能够正常地被反序列化。
serialVersionUID 工作机制:序列化的时候会把当前类的 serialVersionUID 写入序列化的文件中(或其他介质),当反序列化的时候系统回去检测文件中的 serialVersionUID ,看它是否和当前类的 serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,就可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,此时是无法正常序列化的,会报如下错误:
java.io.InvalidClassException: Main; local class incompatible:
stream classdesc serialVersionUID = 87113688280100830474, local class serialVersionUID = 8711368828010083043.
一般来说,需要手动指定 serialVersionUID 的值,也可以让IDE根据当前类的结构自动去生成它的hash值,这样序列化和反序列化时两者的 serialVersionUID 时相同的,因此可以正常进行反序列化。
注意
- 静态成员变量属于类不属于对象,所以不会参与序列化过程。
- 用
transient
关键字标记的成员变量不参与序列化过程。 - 系统默认的序列化过程也是可以改变的,实现
writeObject(ObjectOutputStream out)
、readObject(ObjectInputStream in)
两个方法即可重写默认的序列化和反序列化过程。
Parcelable(Android专用)
Parcelable 也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。
示例:
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
public User createFromParcel(Parcel in) {
return new User(in);
}
public User[] newArray(int size) {
return new User[size];
}
};
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread,currentThread().getContextClassLoader();
}
}
Parcel
Parcel 内部包装了可序列化的数据,可以在 Binder 中自由传输。
Parcelable 序列化过程中需要实现序列化、反序列化和内容描述功能。
- 序列化:由 writeToParcel 方法来完成,最终通过 Parcel 中的一系列 write 方法来完成;
- 反序列化:由 CREATOR 来完成,其内部表明了如何创建序列化对象和数组,并通过 Parcel 的一系列 read 方法来完成反序列化过程;
- 内容描述:由 describeContents 方法来完成,几乎所有情况下这个方法都应该返回 0,仅当当前对象中存在文件描述符时此方法返回 1.
Parcelable 方法说明
- createFromParcel(Parcel in):从序列化后的对象中创建原始对象。
- newArray(int size):创建指定长度的原始对象数组。
- User(Parcel in):从序列化后的对象中创建原始对象。
- writeToParcel(Parcel out, int flags):将当前对象写入序列化结构中,其中 flags 标识有两种:0、1,(PARCELABLE_WRITE_RETURN_VALUE)。为 1 时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为 0。
- describeContents:返回当前对象的内容描述。如果含有文件描述符,返回 1 ,(CONTENTS_FILE_DESCRIPTOR),否则返回 0,几乎所有情况都返回 0。
系统已经提供了很多实现了 Parcelable 接口的类,它们都是可以直接序列化的,比如 Intent、Bundle、Bitmap 等,List、Map 也可以序列化,前提是它们里面的每个元素都是可序列化的。
Serializable 和 Parcelale 对比
两者都能实现序列化,并且都可用于 Intent 间的数据传递。
- Serializable 使用简单,但开销很大,序列化和反序列化过程需要大量 I/O 操作。
- Parcelable 是Android中的序列化方式,更适合Android平台,使用稍微麻烦点,但效率很高,是7Android推荐的序列化方式。
Android开发中,应首选 Parcelable。
Parcelable 主要用在内存序列化上,通过 Parcelable 将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但这个过程会稍显复杂,因此在这两种情况下建议使用 Serializable。