Android序列化经典解析(三)-拨乱反正,堪比窦娥的Serializable

关于网上很多博客提到ParcelableSerializable快,原因大致有两种说法:

  1. Serializable基于反射来做的
  2. Serializable基于磁盘进行序列化,而Parcel基于内存

下面我们先来看第一点,Serializable确实是默认使用反射,默认情况下肯定会比Parcelable慢。默认的好处是我只需要继承自Serializable接口即好,特别简单,然而如果Serializable愿意牺牲简单性,而采取和Parcel一样的代码,在writeObjectreadObject中直接进行流的读写操作呢,这样应该就完全可以绕开反射了。

这里的绕开指的是不再调用ObjectOutputStream.defaultWriteObjectObjectInputStream.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内部新建一个名为mExtrasBundle来存放数据。我们来看看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对象,然后直接调用IntentwriteToParcel方法,因为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<String,Object> val) {
   
     Set<Map.Entry<String,Object>> entries = val.entrySet();
     writeInt(entries.size());
     for (Map.Entry<String,Object> 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);
        }
    }
}

看到这里,我故意留了三个类型的写入:Parcelablebyte[]数组(普通字节流)以及我们的主角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);
    }
}

到这里,那位说AndroidSerializable序列化是基于磁盘的同学站起来,脸打得啪啪响。

看到这里,我就在想如果SerializableParcelable一样,自己来进行写入和读取的操作,到底速度差多少,怎么来做这个实验呢?那无非是把一个类序列化写入Parcel,然后在写出来呗,幸好,我看到已经有个哥们已经干了这个事情:[androidserializationtest][1]

得出的实验结论是:

是的,你没有看错,如果我们和Parcelable一样来自己实现写入和读取,Serializable的速度更快!所以各位Android developer你们真的误会Serializable了。


作者:楚云之南
來源:简书
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值