在接触Parcelable与Serializable之前,我们需要先知道,序列化是什么?为何需要序列化?
对于以上问题,网上已有多种回答,我这边直接复制粘贴并修改一下:
永久的保存对象数据,将对象数据保存在文件当中,或者是磁盘中。
通过序列化操作将对象数据在网络上进行传输,由于网络传输是以字节流的方式对数据进行传输的,因此序列化的目的是将对象数据转换成字节流的形式。
将对象数据在进程之间进行传递,例如:在Android中的两个Activity之间传递对象数据时,需要在当前的Activity中对对象数据进行序列化操作。然后在另一个Activity中需要进行反序列化操作讲数据取出。
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长(即每个对象都在JVM中)但在现实应用中,就可能要停止JVM运行,但有要保存某些指定的对象,并在将来重新读取被保存的对象。这是Java对象序列化就能够实现该功能。(可选择入数据库、或文件的形式保存)
序列化对象的时候只是针对变量进行序列化,不针对方法进行序列化。
在Intent之间,基本数据类型直接进行相关传递即可,但是一旦数据类型比较复杂的时候,就需要进行序列化操作了(例如传递对象或List<>、[]等)。
在知道基本情况后,下面我们先了解一下作为Java语言的老牌序列化接口——
Serializable
Serializable接口是Java提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现的对象的序列化相当简单,只需要在类中implements Serializable即可。
1.简单的使用:
public class TestBean implements Serializable {
private String name;
private int age;
private int gender;//0-女 1-男
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
}
2.用刚才定义的类来做一个简单的实现:
TestBean bean = new TestBean();
bean.setName("张三");
bean.setAge(18);
bean.setGender(1);
Intent intent = new Intent(MainActivity.this, NameActivity.class);
Bundle bundle=new Bundle();
bundle.putSerializable("bean",bean);
intent.putExtra("data", bundle);
startActivity(intent);
//在NameActivity中接受
Bundle bundle=intent.getBundleExtra("data");
//用Serializable进行序列化的时候需要强转类型
TestBean bean = (TestBean) bundle.getSerializable("bean");
在这里如果这个Bean对象未序列化,那么编译器就会提示报错:
3.Serializable的使用就这么简单,需要注意的是静态成员变量属于类但不属于对象,所以不参与序列化过程!
Parcelable
看了Serializable或许你会说,既然Serializable的使用这么简单,那我为什么还要用Parcelable呢?Parcelable 又是个什么鬼?
那么现在我们就来简单的介绍一下Parcelable :
Parcelable是由Google爸爸专为Android开发的一个序列化接口
既然有Serializable为啥还要Parcelable?因为Google爸爸认为Serializable的序列化效率太慢了
那么Parcelable用起来方便吗?不好意思,相对来说比Serializable麻烦很多
那Parcelable比Serializable的序列化效率快多少呢?根据某大牛的测试,同一机型同一类序列化Parcelable比Serializable快10倍以上,具体的比较会在最后慢慢分析
Parcelable到底有多麻烦?下面来看看具体怎么使用吧,还是用刚才的那个Bean类
1.Parcelable的使用(geter和seter方法就不贴出来了):
public class TestBean implements Parcelable {
private String name;
private int age;
private int gender;//0-女 1-男
protected TestBean(Parcel in) {
name = in.readString();
age = in.readInt();
gender = in.readInt();
}
public static final Creator<TestBean> CREATOR = new Creator<TestBean>() {
@Override
public TestBean createFromParcel(Parcel in) {
return new TestBean(in);
}
@Override
public TestBean[] newArray(int size) {
return new TestBean[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeInt(gender);
}
}
可以看到实现了Parcelable的类比Serializable的类多了4个方法,而且还是必须实现的!他们具体都是干嘛的?我们来挨着看!
(1.) protected TestBean(Parcel in) 方法:
可以理解为一个构造方法,把数据从Parcel中read出来。
(2.) public void writeToParcel(Parcel dest, int flags) 方法:
这个方法是把数据write进Parcel中,实际上就是将对象数据序列化成一个Parcel对象(序列化之后成为Parcel对象.以便Parcel容器取出数据)。
这里需要注意的是,read和write的数据顺序必须一致,否则可能会出现name的值为其他(本例中的age或gender)的值。
不过根据编译器自动重载的方法顺序是一致的。
(3.) describeContents()方法:
返回的是一个int类型数据,这个int是返回当前对象的内容描述,几乎所有情况都返回0,仅在当前对象中存在文件描述符时返回1,因此默认为0就可以了。
(4.) public static final Creator CREATOR = new Creator()方法:
将Parcel容器中的数据转换成对象数据,其中又有2个需要实现的方法:
①.public TestBean createFromParcel(Parcel in):
从Parcel容器中取出数据并进行转换。
②.public TestBean[] newArray(int size) :
返回对象数据的大小。
2.以上提到的所有方法都默认即可,但是在这个时候如果直接new这个Bean类的话会报错,因为,没有重写构造方法(Java类的构造方法默认是无参数的)!但由于上面的protected TestBean(Parcel in)方法就类似一个构造方法了,所以想要new一个无参的对象时需要重写构造方法!至于使用上和Serializable的一致,还是贴一下代码吧:
TestBean bean = new TestBean();
bean.setName("张三");
bean.setAge(18);
bean.setGender(1);
Intent intent = new Intent(MainActivity.this, NameActivity.class);
Bundle bundle=new Bundle();
bundle.putParcelable("bean",bean);
intent.putExtra("data", bundle);
startActivity(intent);
//在NameActivity中接受
Bundle bundle=intent.getBundleExtra("data");
//用Parcelable进行序列化的时候不需要强转类型
TestBean bean = bundle.getParcelable("bean");
3.从Parcel的源码中可以看得出来,源码中出现了大量的native关键词(在Android中出现这个关键词十有八九就意味着用到了JNI去调用C/C++代码),调用接口实现序列化的过程,首先是将Parcel(Java)对象转换成Parcel(C++)对象,然后被封装在Parcel中的相关数据由C++底层来完成数据的序列化操作。
关于这一个过程,可以小小的总结一下(其实我并没有去深入的研究源码,只是从别处抄的大牛的总结,有兴趣的可以看看源码):
(1.) 整个读写全是在内存中进行,主要是通过malloc()、realloc()、memcpy()等内存操作进行,所以效率比JAVA序列化中使用外部存储器会高很多。
(2.) 读写时是4字节对齐的,可以从源码中看到#define PAD_SIZE(s) (((s)+3)&~3)这句宏定义就是在做这件事情。
(3.) 如果预分配的空间不够时 newSize = ((mDataSize+len)*3)/2; 会一次多分配50%
(4.) 对于普通数据,使用的是mData内存地址,对于IBinder类型的数据以及FileDescriptor使用的是mObjects内存地址。后者是通过flatten_binder()和unflatten_binder()实现的,目的是反序列化时读出的对象就是原对象而不用重新new一个新对象。
总结
Parcelable与Serializable对比(以下简称P和S):
在内存的使用中,P在性能方面要强于S。
S在序列化操作的时候会产生大量的临时变量,原因是使用了反射机制(并且进行大量的IO操作),从而导致GC的频繁调用,因此在性能上会稍微逊色
P是以Ibinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,Google爸爸推荐使用P,既然是内存方面比价有优势,那么自然就要优先选择。
在读写数据的时候,P是在内存中直接进行读写,而S是通过使用IO流的形式将数据读写入在硬盘上。
在将数据保存在磁盘的时候,仍然需要使用S,因为P无法很好的将数据进行持久化。
不要为了偷懒而大量使用S!作为一位好的程序员,最优先保障的应该是程序的质量而不是为了少写那几行代码而带来更多的问题!
关于S与P的效率比较是比较老的系统版本和机型做的比较了,但从结果上来看,系统、硬件不断提升,两者的效率差距在减小,但是P的效率还是比S高得多。贴一下源文地址:但是我不保证是原作者!
本文的大部分内容为网络上各种贴子的抄录,部分为自己的体会。