以下内容摘抄自作者:任玉刚所著的《Android开发艺术探索》
Android中序列化与反序列化主要有两种方式:Serializable接口和Parcelable接口。我们先来介绍Serializable的使用方法。
Serializable
Serializable是Java提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable实现序列只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程:
private static final long serialVersionUID = 12345678910111213L;
例如下面这个User类,就是已经实现了序列化接口,可被序列化和反序列化的类:
public class User implements Serializable {
private static final long serialVersionUID = 12345678910111213L;
public int userId;
public String userName;
public int isMale;
}
有了序列化类之后,对对象进行序列化和反序列化只需要借助ObjeckOutputString和ObjectInputString即可:
//序列化过程
User user = new User();
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("test.txt"));
output.writeObject(user);
output.close();
//反序列化过程
ObjectInputStream input = new ObjectInputStream(new FileInputStream("test.txt"));
User user1 = (User) input.readObject();
input.close();
其实不指定serialVersionUID也可以完成序列化,但会对反序列化造成影响。
这个serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的类,和当前类的serialVersionUID一致时,才能够反序列化成功。这是因为,序列化的时候,系统会把当前类的serialVersionUID写入序列化文件中(也有可能是其它中介),当反序列化的时候,系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致,则说明两者版本相同,这个时候就可以被成功反序列化,否则则证明当前类相比序列化的类有变动,如成员变量数量、类型,这个时候是无法被反序列化的,会报错提示:
一般来说我们应该手动指定serialVersionUID的值,如:123L,也可以让IDE根据当前类的结构自动去生成它的hash值,这样序列化和反序列化时,两者的serialVersionUID是相同的。
如果不手动指定serialVersionUID的值,使用了自动生成的hash值时,当我们增加删除成员变量时,系统会重新生成hash值,导致serialVersionUID的值发生了改变,最后反序列化就会失败,程序出现crash。反之,如果我们手动指定了serialVersionUID值,但我们成员变量增加或删除,反序列化仍会成功,恢复我们大部分数据。
当然了如果类结构发生了改变,比如修改了类名,修改了成员变量的类型的时候,无论怎样,反序列化都会失败。
需要注意一下:首先静态变量属于类不属于对象,所以不会参加序列化的过程;其次用transient关键字标记的成员变量,也不参与序列化过程。
另外,系统的序列化过程是可以自定义的,只需要在序列化类,如上面的User类中,重写如下两个方法即可,实际上大部分情况我们都不需要去重写:
private void writeObject(ObjectOutputStream stream) throws IOException {
//to do something
}
private void inputObject(ObjectInputStream stream) throws IOException {
//to do something
}
接下来我们讲第二种序列化方式:Parcelable
Parcelable
Parcelable是Android中提供的序列化方式,Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化,并通过Intent和Binder传递。用法如下:
public class User2 implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User2(int userId, String userName, boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
@Override
public int describeContents() {
return 0;
}
@Override
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 Creator<User2> CREATOR = new Creator<User2>() {
@Override
public User2 createFromParcel(Parcel in) {
return new User2(in);
}
@Override
public User2[] newArray(int size) {
return new User2[size];
}
};
private User2(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
}
我们可以看到以上这个类中,出现了一个Parcel对象,在这个Parcel类内部包含了可序列化的数据,可以在Binder中自由传输。
首先序列化功能由writeToParcel()方法来完成。最终通过Parcel的一系列write方法来完成。
反序列化由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化的过程。
describeContents()方法用来实现内容描述功能,几乎在所有情况下,这个方法都返回0,仅当当前对象存在文件描述时,此方法返回1。
需要注意的是User2(Parcel in)这个方法中,由于book是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器,否则会报无法找到类的错误。
详细接口方法介绍请参照如下表格内容:
其实系统已经为我们提供了许多已经实现了Parcelable接口的类,它们都是可以直接序列化的,比如:Intent、Bundle、Bitmap等。同时List和Map也可以序列化,前提是它们里面的每个元素也是可以序列化的。
那么在使用中我们如何选取呢?Serializable是Java序列化接口,由于需要大量的I/O操作,效率会受到影响。而Parcelable属于Android中的序列化方式,因此在 Android平台上效率更高,但缺点也很明显,使用相比前者更麻烦一点,因此还是比较推荐使用Parcelable的方式。
Parcelable主要用在内存序列化上,但是通过Parcelable将对象序列化到存储设备中或者序列化后通过网络传输的过程就会稍显复杂,如果是这两种情况的话,就比较推荐使用Serializable了。