在 Android 中传递一些数据时,我们需要将数据序列化。序列化经常会用到 Serializable 和 Parcelable 这两个类,那么你有没有想过他们的区别是什么呢?这篇文章来分析一下各自的特点及优缺点,并对比一下两种结构的区别。
在讲它们之前,先提一个问题:为什么要序列化?
为什么要序列化
Android开发的时候,我们时长遇到传递对象的需求,但是我们无法将对象的引用传给 Activity 或者 Fragment,我们需要将这些对象放到一个Intent 或者 Bundle 里面,然后再传递,这时候就用到了序列化。所谓序列化就是把 Java 对象转换为字节序列的过程,该字节序列可以被存储到一个储存媒介里或者在网络上进行传输;反序列化就是把字节序列恢复为 Java 对象的过程。但是我们要知道序列化与反序列化仅处理 Java 变量而不处理方法,仅对数据进行处理。
序列化的两种方式
Android中序列化有两种方式:Serializable 以及 Parcelable。其中 Serializable 是 Java 自带的,而 Parcelable 是 Android 专有的。
Serializable
Serializable 是 Java 提供的序列化技术。使用起来非常简单,只需要让某个 Class 实现 Serializable 就可以。在Serializable的文档中提出,Serializable 在序列化运行时会关联一个版本号,用serialVersionUID
来标识,主要用来验证发送者和接收者处理的是不是同一个版本的类。如果版本不一致,则会抛出InvalidClassException
异常。要使用这个标识,需要在类里声明如下:
private/public/protected static final long serialVersionUID = 42L;
虽然不提供这个值也行,Java 会自己生成一个该值,但是最好还是能提供一个。因为受编译器版本不同、内核版本不同等等的影响,有可能同一个类计算出来的serialVersionUID
值不同,就会导致序列化/反序列化失败。
当父类实现了序列化,其子类也会自动实现序列化,不需要再显式实现 Serializable 接口了。
Seralizable 无法序列化静态变量,使用 transient 修饰的对象也无法序列化。所以,当类中有静态变量时,序列化并不会保存该变量。
transient 关键字
transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
实现 Serializable 时,还可以添加writeObject()
和readObject()
方法,虚拟机在序列化和反序列化时,会试图调用这两个方法,通过这两个方法,我们可以控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。举例如下:
class SerializableTest implements Serializable {
private static final long serialVersionUID = 1L;
private String password = "pass";
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private String encrypt() {
...
}
private String decrypt() {
...
}
private void writeObject(ObjectOutputStream out) {
try {
PutField putFields = out.putFields();
System.out.println("原密码:" + this.password);
this.password = encrypt(); // 加密
putFields.put("password", this.password);
System.out.println("加密后的密码" + this.password);
out.writeFields();
} catch (IOException e) {
e.printStackTrace();
}
}
private void readObject(ObjectInputStream in) {
try {
GetField readFields = in.readFields();
Object object = readFields.get("password", "");
this.password = decrypt(object.toString())
System.out.println("解密后的密码:" + this.password);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Parcelable
Parcelable 是 Android 提供的序列化方案。它的出现是为了解决Serializable在序列化的过程中消耗资源严重的问题,但是因为本身使用需要手动处理序列化和反序列化过程,会与具体的代码绑定,使用较为繁琐,一般只获取内存数据的时候使用。
而 Parcelable 依赖于Parcel,Parcel 的意思是包装,实现原理是在内存中建立一块共享数据块,序列化和反序列化均是操作这一块的数据,如此来实现。如图所示:
听起来跟 Linux 的共享内存有没有很像?对,Parcelable 可以用于 Android 的进程间通信。举个简单的例子,应用的 Activity 要与 Service 通信的话,就可以使用 Parcelable。
使用 Parcelable 要稍微麻烦一些。它需要实现以下几点:
- 类本身要实现 Parcelable 接口
- 有一个非空的静态成员变量叫
CREATOR
,并且它要实现Parcelable.Creator
接口- 覆写
createFromParcel(Parcel in)
方法 - 覆写
newArray(int size)
方法
- 覆写
- 覆写
describeContents()
方法 - 覆写
writeToParcel(Parcel dest, int flags)
方法
看起来真的好麻烦。我们还是用代码来解释一下吧:
public class MyParcelable implements Parcelable {
// 要被序列化的数据
private int mData;
// 描述在这个 Parcelable 实例的中含有的特殊对象的类型。
// 例如,如果在对象的 output 中包含一个文件描述符,那它的返回值必须要包含 CONTENTS_FILE_DESCRIPTOR 的 bit 值
// 返回一个 bitmask 值
public int describeContents() {
return 0;
}
// 用二向箔把对象搞成 Parcelable
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
// 一个必须要实现的接口,用来从一个 Parcel 创建你自定义的 Parcelable 实例
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
// 使用之前存储的 Parcel 来创建实例
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
// 创建一个 Parcelable 的数组,所有元素都是 null
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
// 从读取的数据中进行恢复
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}
两种序列化方式的对比
- Serializable 代码量少,Parcelable 代码量多。
- 在内存间传递数据的时候,Parcelable 比 Serializable 性能高、内存开销方面较小,所以推荐使用 Parcelable;而在需要保存数据到本地或者进行网络传输时,使用 Serializable。
- Serializable 在序列化时使用的是反射的技术,会产生大量的临时变量,从而引起频繁的 GC。而 Parcelable 方式的实现原理是将一个完整的对象进行字节化,而字节化之后的每一部分都是 Intent 所支持的数据类型,这样也就实现传递对象的功能了。