序列化,刚入门的开发者可能从字面上看不出这个到底是啥意思,这比较妨碍我们的理解。所以我们需要铭记其定义和作用。
定义:
- Java序列化就是指把Java对象转换为字节序列的过程
- Java反序列化就是指把字节序列恢复为Java对象的过程。
作用:
- 对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。
- 通过序列化可以在进程间传递对象。
从序列化的作用中我们可以看到字节流有利于网络传输或者存储文件,或者进行进程间通信传递对象。实际上,进程间传递对象,也可以理解为序列化的对象保存在了系统的内存中,然后传给另一个对象。由于不同的进程,拥有不同的jvm,所以才需要进行进程间通信,然而,序列化后的对象由于其可用存储为文件或者存在内存,所以可用与进程间通信。
图1 序列化和反序列化过程
Android中总共有两种序列化方式,一个是Java提供的序列化接口Serializable,一个是Android提供的新的序列化方式Parcelable。
Serializable
首先,Java提供的Serialization接口是个空接口,如果一个对象需要序列化,可以实现Serialization接口,然后声明一个SerialVersionUID。
package java.io;
public interface Serializable {
}
因为是空接口,所以非常简单,看起来什么都不用做,实际上是系统帮助我们做了序列化的工作,相当于我这个对象标记为Serialization,系统JVM就知道这个类需要序列化。
public class Person implements Serializable {
private static final long serialVersionUID = 1234567L;
private int age;
private String name;
private boolean isMale;
然后,序列化和反序列化的代码为
//序列化
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(new File("person.txt")));
os.writeObject(person);
os.close();
//反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(new File("person.txt")));
Person person = (Person) in.readObject();
return person;
此过程可以从上图1看出来,重建的对象A’,和A的内容完全一致,但是是两个不同的对象,这和进程间通信一样的现象,系统会通过渠道根据信息新建一个对象,但是进程间的壁垒还是在的,不是直接访问另一个进程。
另外serialVersionUID 的作用是,辅助序列化和反序列化过程。可以使用此字段也可以不用,不用时,系统会根据当前对象生成其hash值,但是如果保存的文件或者网络传输时,一端更改了字段值,就不能成功的反序列化,会报错java.io.InvalidClassException: local class incompatible。而如果指定了这个UID值,比如1L,则两端的这个UID至少是相同的,即使发生了部分字段更改,系统还是会尽最大努力去恢复数据。但是如果改动过大,比如更改了类名、成员变量类型,则还是会反序列化失败。
Parcelable
Parcelable为Android推荐的序列化方法,它是一个接口,和Serialization不同的是,Parcelable不是空接口。这里创建一个demo,代码如下:
public class Person implements Parcelable {
private int age;
private String name;
private boolean isMale;
//从序列化的数据创建原始对象
protected Person(Parcel in) {
age = in.readInt();
name = in.readString();
isMale = in.readByte() != 0;
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
//从序列化对象中创建原始对象
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
//创建指定长度的原始对象数组
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
//返回当前对象的内容描述
@Override
public int describeContents() {
return 0;
}
//将当前对象写入数据化结构parcel中,flag是一般为0|
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(age);
dest.writeString(name);
dest.writeByte((byte) (isMale ? 1 : 0));
}
}
其中writeToParcel和describeContents是Parcelable的接口函数,是必须要实现。然后还有个内部接口类,用以重建对象。从其注释我们可以看到,从Parcel中获取之前writeToParcel的数据,用classloader新建这个Parcelable 实例。
其中Parcel表意是打包的意思,其实就是包装了我们需要传输的数据,然后在Binder中传输,用于跨进程传输数据。
Parcel提供了一套机制,可以将序列化之后的数据写入一个共享内存中,其他进程通过Parcel可以从这块共享内存读出字节流,并反序列化成对象
public interface ClassLoaderCreator<T> extends Creator<T> {
/**
* Create a new instance of the Parcelable class, instantiating it
* from the given Parcel whose data had previously been written by
* {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and
* using the given ClassLoader.
*
* @param source The Parcel to read the object's data from.
* @param loader The ClassLoader that this object is being created in.
* @return Returns a new instance of the Parcelable class.
*/
public T createFromParcel(Parcel source, ClassLoader loader);
}
由于Parcelable是跨进程通信常用的序列化方式,我们可以从多进程的另一个角度看这个过程。
图2 Parcelable 对象的跨进程通信
同样的,重建的对象Person’只是和Person内容相同但是不是同一个对象。
系统中的序列化对象
其实,Parcelable在系统中非常常用,只是由于我们很少分析类的源码,看到的都是他的另一个样子。
就比如Intent类,其作用是一个将要执行的动作的抽象的描述,一般来说是作为参数来使用,由 Intent来协助完成 Android各个组件之间的通讯:
- 启动Activity
通过Context.startActvity();
- 启动Service
通过Context.startService()启动一个服务,或者通过Context.bindService()和后台服务交互;
- 发送Broadcast
通过广播方法Context.sendBroadcasts() / /发给Broadcast Receivers
查看其类源码,可以看出其也是实现了Parcelable的接口,由于刚才的启动Activity, service,发送广播,都可能是跨进程通信,很多数据都是跨进程传输的。
public class Intent implements Parcelable, Cloneable {
另外,由于序列化的对象传输,其成员函数也需要是序列化的对象,Android开发都知道intent也常包装Bundle数据。可以看出Bundle也是实现了序列化的接口的。
public final class Bundle extends BaseBundle implements Cloneable, Parcelable
其他的如ArrayList,HashMap等也是实现了序列化接口的,我们可以慢慢去发现。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
两种序列化方式优缺点
Serializable是Java提供的序列化接口,使用简单,但是开销很大,序列化和反序列化过程需要大量的I/O操作。
Parcelable是Android推荐的序列化方式,效率很高,缺点是使用麻烦。
所以根据二者特点,Serializable一般使用在存储到设备和网络传输场景,后者虽然也能完成,但是很复杂。Parcelable主要用在内存序列化中,比如进程间通信。
文章到这里就结束了,相信大家通过源码和图表能对序列化有个较深的了解, 也对进程间通信有了一定的认识,喜欢的点个赞。下一篇打算写下Android进程间通信之Binder解析,需要了解的可以关注期待。
参考文献:
《Android开发艺术探索》