- 序列化
- 反序列化
- 实现序列化的必要条件
- 序列化的应用情景
- 1)内存中的对象写入到硬盘;
- 2)用套接字在网络上传送对象;
- 3)通过RMI(Remote Method Invoke 远程方法调用)传输对象;
public class Client implements Serializable{
/**
* 生成序列号标识
*/
private static final long serialVersionUID = -2083503801443301445L;
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Stest {
public static void main(String[] args) throws Exception {
//把对象序列化到文件
Client client = new Client();
client.setId(000001);
client.setName("client");
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream("/Users/zejian/Desktop/cache.txt"));
oo.writeObject(client);
oo.close();
//反序列化到内存
ObjectInputStream oi = new ObjectInputStream(new FileInputStream("/Users/zejian/Desktop/cache.txt"));
Client c_back = (Client) oi.readObject();
System.out.println("Hi, My name is " + c_back.getName());
oi.close();
}
}
public class Client implements Serializable{
/**
* 生成序列号标识
*/
private static final long serialVersionUID = -4083503801443301445L;
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 序列化时,
* 首先系统会先调用writeReplace方法,在这个阶段,
* 可以进行自己操作,将需要进行序列化的对象换成我们指定的对象.
* 一般很少重写该方法
* @return
* @throws ObjectStreamException
*/
private Object writeReplace() throws ObjectStreamException {
System.out.println("writeReplace invoked");
return this;
}
/**
*接着系统将调用writeObject方法,
* 来将对象中的属性一个个进行序列化,
* 我们可以在这个方法中控制住哪些属性需要序列化.
* 这里只序列化name属性
* @param out
* @throws IOException
*/
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
System.out.println("writeObject invoked");
out.writeObject(this.name == null ? "zejian" : this.name);
}
/**
* 反序列化时,系统会调用readObject方法,将我们刚刚在writeObject方法序列化好的属性,
* 反序列化回来.然后通过readResolve方法,我们也可以指定系统返回给我们特定的对象
* 可以不是writeReplace序列化时的对象,可以指定其他对象.
* @param in
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
System.out.println("readObject invoked");
this.name = (String) in.readObject();
System.out.println("got name:" + name);
}
/**
* 通过readResolve方法,我们也可以指定系统返回给我们特定的对象
* 可以不是writeReplace序列化时的对象,可以指定其他对象.
* 一般很少重写该方法
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
System.out.println("readResolve invoked");
return this;
}
}
public class NewClient implements Parcelable {
public int id;
public String name;
public User user;
/**
* 当前对象的内容描述,一般返回0即可
* @return
*/
@Override
public int describeContents() {
return 0;
}
/**
* 将当前对象写入序列化结构中
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.id);
dest.writeString(this.name);
dest.writeParcelable(this.user,0);
}
public NewClient() {
}
/**
* 从序列化后的对象中创建原始对象
* @param in
*/
protected NewClient(Parcel in) {
this.id = in.readInt();
this.name = in.readString();
//User是另一个序列化对象,此方法序列需要传递当前线程的上下文类加载器,否则会报无法找到类的错误
this.user=in.readParcelable(Thread.currentThread().getContextClassLoader());
}
/**
* public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。
* 重写接口中的两个方法:
* createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,
* newArray(int size) 创建一个类型为T,长度为size的数组,供外部类反序列化本类数组使用。
*/
public static final Parcelable.Creator<NewClient> CREATOR = new Parcelable.Creator<NewClient>() {
/**
* 从序列化后的对象中创建原始对象
*/
@Override
public NewClient createFromParcel(Parcel source) {
return new NewClient(source);
}
/**
* 创建指定长度的原始对象数组
* @param size
* @return
*/
@Override
public NewClient[] newArray(int size) {
return new NewClient[size];
}
};
}
从代码可知,在序列化的过程中需要实现的功能有序列化和反序列以及内容描述。其中writeToParcel
方法实现序列化功能,其内部是通过Parcel的一系列write方法来完成的,接着通过CREATOR内部对象来实现反序列化,其内部通过createFromParcel
方法来创建序列化对象并通过newArray
方法创建数组,最终利用Parcel的一系列read方法完成反序列化,最后由describeContents完成内容描述功能,该方法一般返回0,仅当对象中存在文件描述符时返回1。同时由于User是另一个序列化对象,因此在反序列化方法中需要传递当前线程的上下文类加载器,否则会报无法找到类的错误。
简单用一句话概括来说就是通过writeToParcel将我们的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成我们的对象。也可以将Parcel看成是一个类似Serliazable的读写流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,这个过程需要我们自己来实现并且写的顺序和读的顺序必须一致。ok~,到此Parcelable接口的序列化实现基本介绍完。
那么在哪里会使用到Parcelable对象呢?其实通过Intent传递复杂类型(如自定义引用类型数据)的数据时就需要使用Parcelable对象,如下是日常应用中Intent关于Parcelable对象的一些操作方法,引用类型必须实现Parcelable接口才能通过Intent传递,而基本数据类型,String类型则可直接通过Intent传递而且Intent本身也实现了Parcelable接口,所以可以轻松地在组件间进行传输。
方法名称 | 含义 |
---|---|
putExtra(String name, Parcelable value) | 设置自定义类型并实现Parcelable的对象 |
putExtra(String name, Parcelable[] value) | 设置自定义类型并实现Parcelable的对象数组 |
public Intent putParcelableArrayListExtra(String name, ArrayList value) | 设置List数组,其元素必须是实现了Parcelable接口的数据 |
除了以上的Intent外系统还为我们提供了其他实现Parcelable接口的类,再如Bundle、Bitmap,它们都是可以直接序列化的,因此我们可以方便地使用它们在组件间进行数据传递,当然Bundle本身也是一个类似键值对的容器,也可存储Parcelable实现类,其API方法跟Intent基本相似,由于这些属于android基础知识点,这里我们就不过多介绍了。
Parcelable 与 Serializable 区别
-
两者的实现差异
Serializable的实现,只需要实现Serializable接口即可。这只是给对象打了一个标记(UID),系统会自动将其序列化。而Parcelabel的实现,不仅需要实现Parcelabel接口,还需要在类中添加一个静态成员变量CREATOR,这个变量需要实现 Parcelable.Creator 接口,并实现读写的抽象方法。 -
两者的设计初衷
Serializable的设计初衷是为了序列化对象到本地文件、数据库、网络流、RMI以便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。而Android的Parcelable的设计初衷是由于Serializable效率过低,消耗大,而android中数据传递主要是在内存环境中(内存属于android中的稀有资源),因此Parcelable的出现为了满足数据在内存中低开销而且高效地传递问题。 -
两者效率选择
Parcelable的性能比Serializable好,在内存开销方面较小,所以Android应用程序在内存间数据传输时推荐使用Parcelable,如activity间传输数据和AIDL数据传递,而Serializable将数据持久化的操作方便,因此在将对象序列化到存储设置中或将对象序列化后通过网络传输时建议选择Serializable(Parcelable也是可以,只不过实现和操作过程过于麻烦并且为了防止android版本不同而导致Parcelable可能不同的情况,因此在序列化到存储设备或者网络传输方面还是尽量选择Serializable接口)。 -
两者需要注意的共同点
无论是Parcelable还是Serializable,执行反序列操作后的对象都是新创建的,与原来的对象并不相同,只不过内容一样罢了。
Android studio 中的快捷生成方式
- Android studio 快捷生成Parcelable代码
在程序开发过程中,我们实现Parcelable接口的代码都是类似的,如果我们每次实现一个Parcelable接口类,就得去编写一次重复的代码,这显然是不可取的,不过幸运的是,android studio 提供了自动实现Parcelable接口的方法的插件,相当实现,我们只需要打开Setting,找到plugin插件,然后搜索Parcelable插件,最后找到android Parcelable code generator 安装即可:
重启android studio后,我们创建一个User类,如下:
然后使用刚刚安装的插件协助我们生成实现Parcelable接口的代码,window快捷键:Alt+Insert,Mac快捷键:cmd+n,如下:
最后结果如下:
- Android studio 快捷生成Serializable的UID
在正常情况下,AS是默认关闭serialVersionUID生成提示的,我们需要打开setting,找到检测(Inspections选项),开启 Serializable class without serialVersionUID 检测即可,如下:
然后新建User类实现Serializable接口,右侧会提示添加serialVersionUID,如下:
最后在类名上,Alt+Enter(Mac:cmd+Enter),快捷代码提示,生成serialVersionUID即可:
最终结果如下:
以上便是Parcelable与Serializable接口的全部内容。