什么是序列化
序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
二进制序列化保持类型保真度,这对于在应用程序的不同调用之间保留对象的状态很有用。例如,通过将对象序列化到剪贴板,可在不同的应用程序之间共享对象。您可以将对象序列化到流、磁盘、内存和网络等等。远程处理使用序列化“通过值”在计算机或应用程序域之间传递对象。
简单地说,“序列化”就是将运行时的对象状态转换成二进制,然后保存到流、内存或者通过网络传输给其他端。
Serializable 和 Parcelable 接口可以完成对象的序列化过程。
使用场景:
- 通过 Intent 和 Binder 传输数据(在内存中序列化)
- 需要把对象持久化到存储设备
- 通过网络传输给其他客户端
Serializable 接口
Serializable 是 Java 所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。
public interface Serializable {
}
想让一个对象实现序列化,只需要这个类实现 Serializable 接口并声明一个 serialVersionUID 即可,实际上,甚至这个 serialVersionUID 也不是必需的,我们不声明这个 serialVersionUID 同样也可以实现序列化,但是这将会对反序列化过程产生影响。
public class User implements Serializable {
private static final long serialVersionUID = 519123124215593L;
public int userId;
public String userName;
public boolean isMale;
}
通过 Serializable 方式来实现对象的序列化,实现起来非常简单,几乎所有工作都被系统自动完成了。如何进行对象的序列化和反序列化也非常简单,只需要采用 ObjectOutputStream 和 ObjectInputStream 即可轻松实现。
// 序列化过程
User user = new User(0, "jake", true);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
// 反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User) in.readObject();
in.close();
上述代码演示了采用 Serializable 方式序列化对象的典型过程,很简单,只需要把实现了 Serializable 接口的 User 对象写到文件中就可以快速恢复了,恢复后的对象 newUser 和 user 的内容完全一样,但是两者并不是同一个对象。
serialVersionUID
serialVersionUID 是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID 只有和当前类的 serialVersionUID 相同才能够正常地被反序列化。
serialVersionUID 的详细工作机制:序列化的时候系统会把当前类的 serialVersionUID 写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的 serialVersionUID,看它是否和当前类的 serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的,报错 InvalidClassException 。
序列化过程中不会保存 static 和 transient 修饰的属性,前者很好理解,因为静态属性属于类管理,不属于对象状态,不会参与序列化过程;而后者则是 Java 的关键字,专门用来标识不序列化的属性。
Parcelable 接口
Parcelable 是 Android 特有的序列化接口
public interface Parcelable {
//writeToParcel() 方法中的参数,用于标识当前对象作为返回值返回
//有些实现类可能会在这时释放其中的资源
public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
//writeToParcel() 方法中的第二个参数,它标识父对象会管理内部状态中重复的数据
public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;
//用于 describeContents() 方法的位掩码,每一位都代表着一种对象类型
public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
//描述当前 Parcelable 实例的对象类型
//比如说,如果对象中有文件描述符,这个方法就会返回上面的 CONTENTS_FILE_DESCRIPTOR
//其他情况会返回一个位掩码
public int describeContents();
//将对象转换成一个 Parcel 对象
//参数中 dest 表示要写入的 Parcel 对象
//flags 表示这个对象将如何写入
public void writeToParcel(Parcel dest, int flags);
//实现类必须有一个 Creator 属性,用于反序列化,将 Parcel 对象转换为 Parcelable
public interface Creator<T> {
public T createFromParcel(Parcel source);
public T[] newArray(int size);
}
//对象创建时提供的一个创建器
public interface ClassLoaderCreator<T> extends Creator<T> {
//使用类加载器和之前序列化成的 Parcel 对象反序列化一个对象
public T createFromParcel(Parcel source, ClassLoader loader);
}
}
实现 Parcelable 接口的类必须有一个 CREATOR 类型的静态变量,下面是一个实例:
public class ParcelableGroupBean implements Parcelable {
private String mName;
private List<String> mMemberNameList;
private User mUser;
/**
* 需要我们手动创建的构造函数
* @param name
* @param memberNameList
* @param user
*/
public ParcelableGroupBean(String name, List<String> memberNameList, User user) {
mName = name;
mMemberNameList = memberNameList;
mUser = user;
}
/**
* 1.内容描述
* @return
*/
@Override
public int describeContents() {
//几乎都返回 0,除非当前对象中存在文件描述符时为 1
return 0;
}
/**
* 2.序列化
* @param dest
* @param flags 0 或者 1
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeStringList(mMemberNameList);
dest.writeParcelable(mUser, flags);
}
/**
* 3.反序列化
*/
public static final Creator<ParcelableGroupBean> CREATOR = new Creator<ParcelableGroupBean>() {
/**
* 反序列创建对象
* @param in
* @return
*/
@Override
public ParcelableGroupBean createFromParcel(Parcel in) {
return new ParcelableGroupBean(in);
}
/**
* 反序列创建对象数组
* @param size
* @return
*/
@Override
public ParcelableGroupBean[] newArray(int size) {
return new ParcelableGroupBean[size];
}
};
/**
* 4.自动创建的的构造器,使用反序列化得到的 Parcel 构造对象
* @param in
*/
protected ParcelableGroupBean(Parcel in) {
mName = in.readString();
mMemberNameList = in.createStringArrayList();
//反序列化时,如果熟悉也是 Parcelable 的类,需要使用它的类加载器作为参数,否则报错无法找到类
mUser = in.readParcelable(User.class.getClassLoader());
}
}
实现了 Parcelable 接口的类在序列化和反序列化时会被转换为 Parcel 类型的数据 。
从上述代码可以看出,在序列化的过程中需要实现的功能有序列化、反序列化和内容描述。Parcel 是一个载体,它可以包含数据或者对象引用,然后通过 IBinder 在进程间传递。
序列化功能由 writeToParcel 方法来完成,最终是通过 Parcel 中的一系列 write 方法来完成的;
反序列化功能由 CREATOR 来完成,其内部标明了如何创建序列化对象和数组,并通过 Parcel 的一系列 read 方法来完成反序列化过程;
内容描述功能由 describeContents 方法来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1。
需要注意的是,在 ParcelableGroupBean(Parcel in) 方法中,由于 User 是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器,否则会报无法找到类的错误。
Serializable 和 Parcelable 的区别:
Serializable 的使用比较简单,但是开销很大,序列化和反序列化过程需要大量 I/O 操作;而 Parcelable 则相对复杂一些,会有4个方法需要实现。
一般在保存数据到 SD 卡或者网络传输时建议使用 Serializable 即可,虽然效率差一些,好在使用方便。
而在运行时数据传递时建议使用 Parcelable,比如 Intent,Bundle 等,Android 底层做了优化处理,效率很高。