Android序列化完全解析(一)-Java Serializable
Android序列化完全解析(二)-Parcelable
Android序列化完全解析(三)-拨乱反正,堪比窦娥的Serializable
终于到了第三篇了,有时候为了写一个问题总有强迫症让自己把整个事情的来龙去脉说清楚,导致东西一拖再拖,本来序列化这个话题我是半年前就想,结果各种原因搁浅,还好,今天状态不错,ok,进入第三篇吧。
关于网上很多博客提到Parcelable
比Serializable
快,原因大致有两种说法:
-
Serializable
基于反射来做的 -
Serializable
基于磁盘进行序列化,而Parcel
基于内存
下面我们先来看第一点,Serializable
确实是默认使用反射,默认情况下肯定会比Parcelable
慢。默认的好处是我只需要继承自Serializable
接口即好,特别简单,然而如果Serializable
愿意牺牲简单性,而采取和Parcel
一样的代码,在writeObject
和readObject
中直接进行流的读写操作呢,这样应该就完全可以绕开反射了。
这里的绕开指的是不再调用
ObjectOutputStream.defaultWriteObject
和ObjectInputStream.defaultReadObject
,这两个方法才是反射调用的大户
我们看一下源码,我们往流中写入一个Serializable
对象时,会调用到writeSerialData
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {
slotDesc.invokeWriteObject(obj, this);
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
可以看到,如果有writeObject
方法,那么序列化时不会去调用默认的序列化方法defaultWriteFields
,这也说明了如果我们在类中添加了writeObject
方法,但是没有调用defaultWriteObject
,那么不会有任何东西写入流中。
同样的,如果我们在类中加入readObject
方法,那么反序列化时就不会去调用默认的defaultReadObject
,这样就避免了序列化过程中频繁通过反射去获取域。
所以当我们在比较这两个方式的时候,最好对Serializable
公平一点!!
再来看第二点,Serializable
基于磁盘的序列化...
嗯,确实我不太明白什么叫基于磁盘的序列化,我只知道序列化是把对象变成二进制流,这个二进制流可以通过FileOutputStream
放到外存中去,也可以通过ByteArrayOutputStream
写到内存中,序列化的过程必然是在内存中进行的。Serializable
序列化之后的结果你可以放到任意的位置,内存、外存、网络,因为在Serializable
之后的字节流中已经带上了足够的信息去进行验证一个需要反序列化的类是否满足定义。而Parcel
虽然也是把数据转换成字节流,但是它的应用空间很窄,原因是在流中写入的类描述信息仅仅是一个类名,所以尽量不要用Parcel做持久化存储!!
我们来看一下在Android
中,如果我们使用Serializable
来进行进程间数据通信是怎样的流程。比如最常见的场景启动Activity
:
Intent intent = new Intent();intent.putExtra("testKey",new SerailableObj());
startActivity(intent);
我们自己定义的类SerailableObj
实现了Serializable
接口,intent.putExtra
会在Intent
内部新建一个名为mExtras
的Bundle
来存放数据。我们来看看Android
中是如何传递Serializable
对象的。
bundle.putSerializable()
:只是把数据放到了Bundle
的一个map
中,那什么时候通过Parcel
去写入到共享内存中呢?跟一下startActivity
源码(Android 4.4),一路调用到ActivityManagerNative.startActivity
:
public int startActivity(IApplicationThread caller, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
Parcel data = Parcel.obtain(); //在这里生成了Parcel实例
//把Intent写入到Parcel中
intent.writeToParcel(data, 0);
//省略。。。。
}
这里新建了一个Parcel
对象,然后直接调用Intent
的writeToParcel
方法,因为Intent
本身就实现了Parcelable
接口。
Intent.writeToParcel:
public void writeToParcel(Parcel out, int flags) {
//省略其它代码。。。
//Intent中的Bundle写入Parcel流中
out.writeBundle(mExtras);
//省略其它代码。。。
}
下面的调用比较复杂,我这里只给出一个调用路径,但是代码灰常简单,上文书说到我们调用的Parcel.writeBundle(mExtras)
将Intent
的参数写入流中,
Parcel.writeBundle(Bundle)-->Bundle.writeToParcel(Parcel)-->Parcel. writeMapInternal(Map)
这个Map
还是我们之前调用Intent.putExtra
时,数据的Bundle
内部的一个Map
,
void More ...writeMapInternal(Map val) {
Set> entries = val.entrySet();
writeInt(entries.size());
for (Map.Entry e : entries) {
//终于把我们的SerailableObj对象写入
writeValue(e.getKey());
writeValue(e.getValue());
}
}
Parcel.writeValue
:
public final void writeValue(Object v) {
//直接判断V的类型,省略茫茫多类型 留下三个主角
if (v instanceof Parcelable) {
writeInt(VAL_PARCELABLE);
writeParcelable((Parcelable) v, 0);
}else if (v instanceof byte[]) {
writeInt(VAL_BYTEARRAY);
writeByteArray((byte[]) v);
}else {
Class> clazz = v.getClass();
if (clazz.isArray() && clazz.getComponentType() == Object.class) {
// Only pure Object[] are written here, Other arrays of non-primitive types are
// handled by serialization as this does not record the component type.
writeInt(VAL_OBJECTARRAY);
writeArray((Object[]) v);
} else if (v instanceof Serializable) {
// Must be last
writeInt(VAL_SERIALIZABLE);
writeSerializable((Serializable) v);
} else {
throw new RuntimeException("Parcel: unable to marshal value " + v);
}
}
}
看到这里,我故意留了三个类型的写入:Parcelable
、 byte[]
数组(普通字节流)以及我们的主角Serializable
。如果是Parcelable
类型,那么它必然会调用我们在Parcelable
接口中定义的writeToParcel
方法,然后我们可以在里面直接像流里面写入int string
等。如果是字节流,那么直接进行内存复制即可;如果是Serializable
呢?聪明的你可能已经猜到了,是的,就是直接通过Java
序列化机制把对象转换成字节流,然后调用writeByteArray
!!
public final void writeSerializable(Serializable s) {
if (s == null) {
writeString(null);
return;
}
String name = s.getClass().getName();
writeString(name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(s);
oos.close();
writeByteArray(baos.toByteArray());
} catch (IOException ioe) {
throw new RuntimeException("Parcelable encountered " +
"IOException writing serializable object (name = " + name +
")", ioe);
}
}
到这里,那位说Android
中Serializable
序列化是基于磁盘的同学站起来,脸打得啪啪响。
看到这里,我就在想如果Serializable
和Parcelable
一样,自己来进行写入和读取的操作,到底速度差多少,怎么来做这个实验呢?那无非是把一个类序列化写入Parcel
,然后在写出来呗,幸好,我看到已经有个哥们已经干了这个事情:[androidserializationtest][1]
得出的实验结论是:
是的,你没有看错,如果我们和Parcelable
一样来自己实现写入和读取,Serializable
的速度更快!所以各位Android developer
你们真的误会Serializable
了。
好了,这三篇文章终于写完了,感觉完成了一件大事情了,周末可以好好撸个串了,哈哈~~
[1]:https://bitbucket.org/afrishman/androidserializationtest