Android序列化Serializable,Parcelable接口的应用,及可能的坑

备忘:

王小二 - 个人中心 - 云+社区 - 腾讯云

一次Binder通信,最大传输多少数据:[007]一次Binder通信最大可以传输多大的数据? - 简书

[006]匿名共享内存(Ashmem)的使用 - 简书

什么是序列化?

简单说就是将数据结构或者对象,转换成可以存储或者传输的数据格式的一个过程,也即是把数据结构,对象转成二进制串的过程.

序列化的是对象,确切说是对象中的变量,不是方法,不是类.

为什么需要序列化?

在操作系统底层,数据是以字节序列传递,所以如果要传递对象,就需要序列化,反序列化操作.

所以,进程间通信,本地数据存储,网络数据传输都需要序列化的支持.

序列化的实现方式:

一种java api提供的Serializable接口,

一种是android提供的Parcelable接口.

那些情况下需要序列化?

1,需要永久保存对象数据,

2,对象数据需要在网络上传输,

3,对象数据需要在进程之间传递.

Android程序中序列化是用Serializable,还是用Parcelable?

通过Serializable序列化,多引用情况下,单例模式下的坑怎么填?

Serializable是Java 提供的序列化接口, 这是一个空接口.

public interface Serializable {

}

一个需要序列化的类需要实现这个接口,实现了这个接口的子类,也自动是可以序列化的.

但是反过来不成立,就是说子类实现了序列化,不代表其父类自动可以序列化.

public class SerializableDemo implements Serializable {
    //这个id唯一标示了一个可以序列化的类,可以同通过黄色灯泡去生成.它的主要作用是处理序列化,反序列化时类的不同版本间的
    // 兼容性问题
    private static final long serialVersionUID = -6690368706839335561L;
    //用transient标记不参与序列化的变量.
    private transient int nonSerial = 1;
    //序列化保存的是对象的状态,静态成员是属于类的,所以不参与序列化.
    private static int nonStaticSerial = 1;

    //除基本数据类型外,对象类型如需要参与序列化,也要实现Serializable接口.如果这个ObjectType不想参与序列化,
    // 需要保证他有一个无参数的构造函数,因为反序列化时,需要调用他的无参构造函数去重建对象.
    private ObjectType ot = new ObjectType();
}

实现Serializable接口,默认是通过ObjectOutputStream,ObjectInputStream实现序列化,反序列化.

如果想才序列化时做一些私有的操作,可以通过实现Externalizable 接口.通过重写其中的writeExternal, readExternal来实现.

public class ExternalizableDemo implements Externalizable {
    private String str = "test";
    @Override
    public void writeExternal(ObjectOutput objectOutput) throws IOException {
        objectOutput.writeObject(str);
    }

    @Override
    public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
        String strTemp = (String)objectInput.readObject();
    }
}

下面看下序列化的步骤及原理.

1,将对象实例相关的类元数据输出.

2,递归的输出类的父类描述.

3,类元数据处理完之后,从最顶层的父类开始输出对象实例的实际数据.

4,从上到下递归输出实例数据.

序列化后的数据是二进制的字节流,可以通过ObjectInputStream输出到一个文件来查看:

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));

oos.writeObject(obj);

其中的ACED 是魔数,说明这个文件使用了序列化协议,

0005 表示了序列化协议的版本,

0x73 声明这是一个新的对象

0x72 声明这里开始一个新的class

002e 表明了class名字的长度

更多的含义可以参考:ObjectStreamConstants.java中的定义.

public interface ObjectStreamConstants {

    /**
     * Magic number that is written to the stream header.
     */
    final static short STREAM_MAGIC = (short)0xaced;

    /**
     * Version number that is written to the stream header.
     */
    final static short STREAM_VERSION = 5;

    /* Each item in the stream is preceded by a tag
     */

    /**
     * First tag value.
     */
    final static byte TC_BASE = 0x70;

    /**
     * Null object reference.
     */
    final static byte TC_NULL =         (byte)0x70;

    /**
     * Reference to an object already written into the stream.
     */
    final static byte TC_REFERENCE =    (byte)0x71;

    /**
     * new Class Descriptor.
     */
    final static byte TC_CLASSDESC =    (byte)0x72;

    /**
     * new Object.
     */
    final static byte TC_OBJECT =       (byte)0x73;

    /**
     * new String.
     */
    final static byte TC_STRING =       (byte)0x74;

    /**
     * new Array.
     */
    final static byte TC_ARRAY =        (byte)0x75;

…
}
看看序列化的实现过程:
public ObjectOutputStream(OutputStream out) #ObjectOutputStream.java{

}
//序列化一个对象时,走的是这里,只列出关键代码.
public final void writeObject(Object obj) #ObjectOutputStream.java{
	writeObject0(obj, false);
}

序列化可能会有一个坑:

这个函数中的第二个参数 boolean unshared,涉及了引用的多次写入问题,默认是false,如果写为true,就表示要写入的对象在流中总是作为一个新的,独一无二的对象,而默认为false时,如果多次写入一个对象,实际后面的写入看不到预期效果.如:

SerializableDemo serializableDemo = new SerializableDemo();
serializableDemo.setNonSerial(100);  nonSerial
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(serializableDemo);
serializableDemo.setNonSerial(200); 
oos.writeObject(serializableDemo);
虽然后面同setNonSerial(200)把nonSerial的值改成200,但是反序列化后得到的nonSerial值仍然是100,
如果这样调用:
serializableDemo.setNonSerial(200); 
oos.writeUnshared(serializableDemo);

反序列化后就是正常的.

这个也是writeUnshared(Object obj) 的实现,当然也可以通过reset() 来达到预期.

出现这个情况的原因是:默认情况下,对一个一个实例的多个应用,为了节省空间,只会写入一次,后面会追加几个字节代表已经写入的实例的引用.

private void writeObject0(Object obj, boolean unshared)#ObjectOutputStream.java{
	//根据对象的类型,走不同分支,以自定义类型为例,会判断其是否实现Serializable接口,
// obj instanceof Serializable
            if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
            } else if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else {
//如果没有实现Serializable接口,会抛异常.
	    throw new NotSerializableException(cl.getName());
	}
}
private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)#ObjectOutputStream.java{
//这里判断是实现的Externalizable还是Serializable
            if (desc.isExternalizable() && !desc.isProxy()) {
//实现了Externalizable接口,会调用其writeExternal方法.
                writeExternalData((Externalizable) obj);
            } else {
//实现了Serializable接口 的,
                writeSerialData(obj, desc);
            }
}
private void writeSerialData(Object obj, ObjectStreamClass desc)#ObjectOutputStream.java{
       ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
        for (int i = 0; i < slots.length; i++) {
            ObjectStreamClass slotDesc = slots[i].desc;
//这里去判断是否重写了writeObject,readObject方法,
            if (slotDesc.hasWriteObjectMethod()) {
                PutFieldImpl oldPut = curPut;
                curPut = null;
                SerialCallbackContext oldContext = curContext;

                if (extendedDebugInfo) {
                    debugInfoStack.push(
                        "custom writeObject data (class \"" +
                        slotDesc.getName() + "\")");
                }
                try {
                    curContext = new SerialCallbackContext(obj, slotDesc);
                    bout.setBlockDataMode(true);
                    slotDesc.invokeWriteObject(obj, this);
                    bout.setBlockDataMode(false);
                    bout.writeByte(TC_ENDBLOCKDATA);
                } finally {
                    curContext.setUsed();
                    curContext = oldContext;
                    if (extendedDebugInfo) {
                        debugInfoStack.pop();
                    }
                }

                curPut = oldPut;
            } else {
//否则调用默认的实现
                defaultWriteFields(obj, slotDesc);
            }
        }
}

上面的函数中需要说明一点的是,实现了Serializable接口的类,是否实现了writeObject,readObject方法是通过反射来判断,并且这两个writeObject,readObject方法,也不是Serializable接口中声明的,所以如果要去实现这两个方法,并被调用到,需要有正确的函数签名:

 private ObjectStreamClass(final Class<?> cl) #ObjectStreamClass.java{
                        writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
                        readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
}

通常什么情况下,可能需要自己去实现writeObject,readObject这两个方法?

就是需要自己去控制需要序列化的内容的时候.

比如说自定义的类中有一个列表,只想序列化这个列表的部分元素时,可以考虑重新实现这两个方法.

一个例子就是ArrayList.java的实现:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//这里先声明 transient,不参与序列化,
 transient Object[] elementData; 
//然后重新实现 writeObject,根据数组中实际有多少成员,来保存其状态.
   private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
}
针对单例模式,反序列化的一个坑:
class Single implements Serializable {
    private static final long serialVersionUID = 1L;
    private Single(){
    }

    private static Single single;

    private static class InnerClass {
        private static Single single = new Single();
    }

    public static Single getInstance() {
        return Single.InnerClass.single;
    }

/*    public static Single getInstance(){
        if ( single == null ) {
            synchronized (Single.class) {
                if ( single == null ) {
                    single = new Single();
                }
            }

        }
        return single;
    }*/

//如果重新实现这个readResolve方法,序列化,反序列化后得到的不是同一个实例,重新实现这个方法后,在反序列化时,会通过这个方法的返回值替换readObject的结果.
    private Object readResolve() {
        return Single.InnerClass.single;
    }
}

从Serializable的序列化,反序列化的代码,可以看到整个过程有较多的反射调用,也出创建很多的临时变量,这也是他效率偏低,及会产生内存碎片的原因.

再看Android中提供的Parcelable接口.它是基于内存来读写的,所以它的效率要比Serializable基于磁盘读写高很多,这是android中跨进程传递对象更多使用Parcelable的原因.比如Intent中传递数据使用的Bundle.

Parcelable序列化时把对象打包成一个Parcel对象,反序列化时再从Parcel中还原成Java对象.Parcel提供了一种机制,可以将序列化后的数据写入到一块共享内存中,其他进程可以从这块共享内存中读出字节流,完成反序列化.Parcel实际是通过IBinder来发送数据,接受数据的.

如果序列化的数据需要存硬盘,应该使用Serializable,

如果是android中跨进程传递数据,并且数据量较少,应该使用Parcelable,前面说Parcel的数据发送是基于Binder机制的,而Binder机制映射的那块共享内存是有大小限制的,这个限制好像最大是4M,一个Binder线程能分配的缓冲区就更小了,所以使用Parcelable序列化的数据不能太大.

public class ParcelableDemo implements Parcelable {

    //描述当前实例的对象类型,
    @Override
    public int describeContents() {
        return 0;
    }

    //将对象包装成一个Parcel对象.
    @Override
    public void writeToParcel(Parcel dest, int flags) {

    }

    //用于反序列化,将parcel对象还原成java对象.
    public static final Parcelable.Creator<ParcelableDemo> CREATOR =
            new Parcelable.Creator<ParcelableDemo>() {
                @Override
                public ParcelableDemo createFromParcel(Parcel source) {
                    return null;
                }

                @Override
                public ParcelableDemo[] newArray(int size) {
                    return new ParcelableDemo[0];
                }
            };
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值