为什么要序列化与反序列化
当我们的应用需要从网络获取包括文本、图片、音频、视频等资源时,这些数据都会以二进制序列的形式在网络上传送。发送方需要把这些Java对象转换为字节序列,然后在网络上传送,接收方需要从字节序列中恢复出Java对象。使用序列化之后才能将对象保存在本地,也才能将对象在网络上传输。
什么是序列化与反序列化
Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。
序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。
反序列化:客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。
哪些场景需要序列化
永久性保存对象,保存对象的字节序列到本地文件中;
对象在网络中传递;
对象在 IPC 间传递。
Android中的两种序列化机制
- 实现 Serializable 接口
- 实现 parcelable 接口
Serializable 和 Parcelable 的区别
Serializeble 是 java 的序列化方式,Parcelable 是 Android 特有的序列化方式;
在使用内存的时候,Parcelable 比 Serializable 性能高,所以推荐使用 Parcelable。
Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。
Parcelable 不能使用在要将数据存储在磁盘上的情况,因为 Parcelable 不能很好的保证数据的持续性在外界有变化的情况下。尽管 Serializable 效率低点, 也不提倡用,但在这种情况下,还是建议你用 Serializable。
Serializeble 序列化的方式比较简单,直接集成一个接口就好了,而 parcelable 方式比较复杂,不仅需要集成 Parcelable 接口还需要重写里面的方法。
Serializable接口
public interface Serializable {
}
Serializable 是 Java 提供的序列化接口,它是一个空接口,对,没有声明任何方法,没有任何代码实现。
Serializable 用来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。只要你的类打上了“Serializable”这个标记,就可以使用流把它写出去,如果没有打上这个标记你就去用流写的话,它就会抛出一个异常。
Serializable 有以下几个特点:
- 可序列化类中,未实现 Serializable 的属性状态无法被序列化/反序列化
- 也就是说,反序列化一个类的过程中,它的非可序列化的属性将会调用无参构造函数重新创建
- 因此这个属性的无参构造函数必须可以访问,否者运行时会报错
- 一个实现序列化的类,它的子类也是可序列化的
- Serializable的对象里的对象类,也需要实现Serializable
serialVersionUID
一般在实体类中,我们最好再定义一个serialVersionUID字段,值可以随便写一个,比如:
private static final long serialVersionUID = 1L;
比如,我们的User类,之前已经被序列化到文件里,现在由于需求变更,需要在User类中再加一个字段userId,那么,加好之后,此时再去反序列化之前已经保存在文件里的对象,会抛出异常,就是因为java检测到现在的serialVersionUID与保存在文件里的serialVersionUID不一致,由于我们之前没有手动定义serialVersionUID这个字段,所以这个字段是java自己算的一个值,每次增加或者删除字段,这个值都会变化,如果,我们手动写死的话,这个值就不变了,也不会报异常了。
Parcelable 接口
parcel的中文意思是包裹,parcelable是可打包的意思。
实现了 Parcelable 接口的类在序列化和反序列化时会被转换为 Parcel 类型的数据 。Parcel 是一个载体,它可以包含数据或者对象引用,然后通过 IBinder 在进程间传递。
Parcelable 接口:
实现Parcelable接口主要需要实现writeToParcel方法和createFromParcel这两个,一个是将对象写为Parcel对象,一个是将Parcel对象转为本对象,内部需要自己按照规则实现。
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);
}
}
transient
假如说User类中有个字段我不想序列化,比如那个password字段,他保存的是用户的密码,这是敏感数据,如果序列化之后,万一泄漏了怎么办,这不要出事了,那我们怎么办呢,其实很简单,只需要在这个字段之前加transient关键字就可以了:
private transient String password;
实现Parcelable示例:
public class ParcelableGroupBean implements Parcelable {
private String name;
private List<String> list;
private User user;
/**
* 4.自动创建的的构造器,使用反序列化得到的 Parcel 构造对象
* @param in
*/
protected ParcelableGroupBean(Parcel in) {
name = in.readString();
list = in.createStringArrayList();
user = in.readParcelable(User.class.getClassLoader()); //读取一个类对象是,需要传入该类的classloader
}
/**
* 内容描述
* @return
*/
@Override
public int describeContents() {
//几乎都返回 0,除非当前对象中存在文件描述符时为 1
return 0;
}
/**
* 序列化
* @param dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written.
* May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(name);
dest.writeStringList(list);
dest.writeParcelable(user, flags); //写入对象需要用 writeParcelable
}
/**
* 反序列化
*/
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];
}
};
}
class User implements Parcelable {
private String name;
private int age;
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
}
如何选择哪种序列化方式
一般在保存数据到 SD 卡或者网络传输时建议使用 Serializable 即可,虽然效率差一些,好在使用方便。
而在运行时数据传递时建议使用 Parcelable,比如 Intent,Bundle 等,Android 底层做了优化处理,效率很高。
为什么Activity间传递对象需要序列化
activity间传递对象使用intent,Intent 在启动其他组件时,会离开当前应用程序进程,进入 ActivityManagerService 进程 – intent.prepareToLeaveProcess()。 这也就意味着,Intent 所携带的数据要能够在不同进程间传输。
参考:
https://blog.csdn.net/weixin_47933729/article/details/112389099
https://blog.csdn.net/A448955639/article/details/71374199