Android -序列化扫盲

309 阅读8分钟

序列化产生的背景

由于在系统底层,数据的传输形式是简单的字节序列形式传递,即在底层,系统不认识对象,只认识字节序列,而为了达到进程通讯的目的,需要先将数据序列化,而序列化就是将对象转化字节序列的过程。相反地,当字节序列被运到相应的进程的时候,进程为了识别这些数据,就要将其反序列化,即把字节序列转化为对象

序列化

将数据结构或对象转成二进制串的过程

反序列化

将序列化产生的二进制串转成数据结构或对象的过程。

序列化/反序列化的目的

序列化:主要是用于网络传输,数据持久化,一般序列化也称为编码(Encode) 反序列化:主要是用于从网络,磁盘上读取字节数组还原成原始对象,一般反序列化也称为解码(Decode)

目的:

  • 永久的保存对象数据
  • 通过序列化操作将对象数据在网络上进行传输
  • 将对象数据在进程之间传递
  • java平台允许我们在内存中创建可复用的java对象,但一般情况下,只有当jvm处于运行时,这些对象才可能存在,但是现实应用中,可能有jvm停止运行后仍然要保留指定的对象,并且在将来重新读取被保存的对象,这样就可以用java对象序列化实现
  • 序列化对象的时候只是针对变量进行序列化,不针对方法进行序列化
  • 在intent之间传递数据,如果传递的数据类型比较复杂,就需要进行序列化操作。

Android中常用的序列化和反序列化

  • Serializable接口(java)

    标识当前类可以被ObjectOutputStream序列化,以及被ObjectInputStream反序列化

  • Parcelable接口(android sdk)

    通过Parcel写入和恢复数据的,并且必须要有一个非空的静态变量 CREATOR

Serializable

Serializable的特点
  • 可序列化类中,未实现 Serializable 的属性状态无法被序列化/反序列化
  • 也就是说,反序列化一个类的过程中,它的非可序列化的属性将会调用无参构造函数重新创建
  • 因此这个属性的无参构造函数必须可以访问,否者运行时会报错
  • 一个实现序列化的类,它的子类也是可序列化的
serialVersionUID

用来表明类的不同版本间的兼容性。如果你修改了此类, 要修改此值。否则以前用老版本的类序列化的类恢复时会报错:InvalidClassException

序列化步骤
  • 将对象实例相关的类元数据输出。
  • 递归地输出类的超类描述直到不再有超类。
  • 类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
  • 从上至下递归输出实例的数据
二进制打开序列化的文件

image.png

  • AC ED: STREAM_MAGIC. 声明使用了序列化协议.
  • 00 05: STREAM_VERSION. 序列化协议版本.
  • 0x73: TC_OBJECT. 声明这是一个新的对象.
  • 0x72: TC_CLASSDESC. 声明这里开始一个新Class。
  • 00 2e: Class名字的长度.
序列化(writeObject)源码分析

->>>进入java.io.ObjectOutputStream类

1.ObjectOutputStream的构造函数设置enableOverride = false

public ObjectOutputStream(OutputStream out) throws IOException {
   ...
   this.enableOverride = false;
   ...
}
复制代码

2.序列化的时候进入writeObject0方法中

public final void writeObject(Object obj) throws IOException {
    if (this.enableOverride) {
        this.writeObjectOverride(obj);
    } else {
        try {
            this.writeObject0(obj, false);
        } catch (IOException var3) {
            if (this.depth == 0) {
                this.writeFatalException(var3);
            }

            throw var3;
        }
    }
}
复制代码

3.writeObject0会更具类型进入writeOrdinaryObject这个方法

private void writeObject0(Object obj, boolean unshared) throws IOException {
   
              。。。
                if (obj instanceof String) {
                    this.writeString((String)obj, unshared);
                    return;
                } else {
                    if (cl.isArray()) {
                        this.writeArray(obj, desc, unshared);
                    } else if (obj instanceof Enum) {
                        this.writeEnum((Enum)obj, desc, unshared);
                    } else {
                        if (!(obj instanceof Serializable)) {
                            if (extendedDebugInfo) {
                                throw new NotSerializableException(cl.getName() + "\n" + this.debugInfoStack.toString());
                            }

                            throw new NotSerializableException(cl.getName());
                        }

                        this.writeOrdinaryObject(obj, desc, unshared);
                    }
                    break;
                }
        。。。

}
复制代码

4.writeOrdinaryObject主要执行逻辑

private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) throws IOException {
。。。
        if (desc.isExternalizable() && !desc.isProxy()) {
      
          //如果对象实现了Externalizable接口,那么执行
          //writeExternalData((Externalizable) obj)方法
            this.writeExternalData((Externalizable)obj);
        } else {
            this.writeSerialData(obj, desc);
        }
。。。
}
复制代码

5.我们进入writeSerialData方法

//最终写序列化的方法
private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException {
    ClassDataSlot[] slots = desc.getClassDataLayout();

    for(int i = 0; i < slots.length; ++i) {
        ObjectStreamClass slotDesc = slots[i].desc;
        //判断目标类中是否有writeObject方法,如果有的话调用目标类中
        if (slotDesc.hasWriteObjectMethod()) {
            ObjectOutputStream.PutFieldImpl oldPut = this.curPut;
            this.curPut = null;
            SerialCallbackContext oldContext = this.curContext;
            if (extendedDebugInfo) {
                this.debugInfoStack.push("custom writeObject data (class "" + slotDesc.getName() + "")");
            }

            try {
                this.curContext = new SerialCallbackContext(obj, slotDesc);
                this.bout.setBlockDataMode(true);
                slotDesc.invokeWriteObject(obj, this);
                this.bout.setBlockDataMode(false);
                this.bout.writeByte(120);
            } finally {
                this.curContext.setUsed();
                this.curContext = oldContext;
                if (extendedDebugInfo) {
                    this.debugInfoStack.pop();
                }

            }

            this.curPut = oldPut;
        } else {
        //调用默认的
            this.defaultWriteFields(obj, slotDesc);
        }
    }

}
复制代码

6 这里会涉及到ObjectOutputStream类,会寻找目标类中的私有的writeObject(readObject)方法,赋值给变量writeObjectMethod(readObjectMethod)

private ObjectStreamClass(final Class<?> cl) {
 。。。
    if (this.serializable) {
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Void run() {
                if (ObjectStreamClass.this.isEnum) {
                    ObjectStreamClass.this.suid = 0L;
                    ObjectStreamClass.this.fields = ObjectStreamClass.NO_FIELDS;
                    return null;
                } else if (cl.isArray()) {
                    ObjectStreamClass.this.fields = ObjectStreamClass.NO_FIELDS;
                    return null;
                } else {
                    ObjectStreamClass.this.suid = ObjectStreamClass.getDeclaredSUID(cl);

                    try {
                        ObjectStreamClass.this.fields = ObjectStreamClass.getSerialFields(cl);
                        ObjectStreamClass.this.computeFieldOffsets();
                    } catch (InvalidClassException var2) {
                        ObjectStreamClass.this.serializeEx = ObjectStreamClass.this.deserializeEx = new ObjectStreamClass.ExceptionInfo(var2.classname, var2.getMessage());
                        ObjectStreamClass.this.fields = ObjectStreamClass.NO_FIELDS;
                    }

                    if (ObjectStreamClass.this.externalizable) {
                        ObjectStreamClass.this.cons = ObjectStreamClass.getExternalizableConstructor(cl);
                    } else {
                        ObjectStreamClass.this.cons = ObjectStreamClass.getSerializableConstructor(cl);
                        ObjectStreamClass.this.writeObjectMethod = ObjectStreamClass.getPrivateMethod(cl, "writeObject", new Class[]{ObjectOutputStream.class}, Void.TYPE);
                        ObjectStreamClass.this.readObjectMethod = ObjectStreamClass.getPrivateMethod(cl, "readObject", new Class[]{ObjectInputStream.class}, Void.TYPE);
                        ObjectStreamClass.this.readObjectNoDataMethod = ObjectStreamClass.getPrivateMethod(cl, "readObjectNoData", (Class[])null, Void.TYPE);
                        ObjectStreamClass.this.hasWriteObjectData = ObjectStreamClass.this.writeObjectMethod != null;
                    }

                    ObjectStreamClass.this.domains = ObjectStreamClass.this.getProtectionDomains(ObjectStreamClass.this.cons, cl);
                    ObjectStreamClass.this.writeReplaceMethod = ObjectStreamClass.getInheritableMethod(cl, "writeReplace", (Class[])null, Object.class);
                    ObjectStreamClass.this.readResolveMethod = ObjectStreamClass.getInheritableMethod(cl, "readResolve", (Class[])null, Object.class);
                    return null;
                }
            }
        });
    } else {
        this.suid = 0L;
        this.fields = NO_FIELDS;
    }

  。。。
}
复制代码

image.png

Serializable注意事项
  • static静态变量和transient 修饰的字段是不会被序列化的
  • serialVersionUID问题
  • 如果某个序列化类的成员变量是对象类型,则该对象类型的类必须实现序列化
  • 子类实现了序列化,父类没有实现序列化,父类中的字段丢失问题
  • 单利模式的序列化需要重写readResolve这个方法,如果不重写的话,会导致单例模式在序列化->反序列化后失败

Parcelable

Parcelable实现过程主要分为_序列化,反序列化,描述_三个过程,下面分别介绍下这三个过程

Parcel是什么

在介绍Parcelable之前我们需要先了解Parcel是什么?

Parcel其实就是包装了我们需要传输的数据,然后在Binder中传输,也就是用于跨进程传输数据

简单来说,Parcel提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象

image.png

Parcel可以包含原始数据类型(用各种对应的方法写入,比如writeInt(),writeFloat()等),可以包含Parcelable对象,它还包含了一个活动的IBinder对象的引用,这个引用导致另一端接收到一个指向这个IBinder的代理IBinder。

Parcelable通过Parcel实现了read和write的方法,从而实现序列化和反序列化

常见方法
public class Sync implements Parcelable {


    protected Sync(Parcel in) {
    }
    //反序列化
    public static final Creator<Sync> CREATOR = new Creator<Sync>() {
        @Override
        public Sync createFromParcel(Parcel in) {
            return new Sync(in);
        }

        @Override
        public Sync[] newArray(int size) {
            return new Sync[size];
        }
    };
    //描述
    @Override
    public int describeContents() {
        return 0;
    }
    // 序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) {
    }
}
复制代码

describeContents :负责文件描述。只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以

writeToParcel :返回了Parcel,所以我们可以直接调用Parcel中的write方法,基本的write方法都有,对象和集合比较特殊下面单独讲,基本的数据类型除了boolean其他都有,Boolean可以使用int或byte存储

CREATOR :通过匿名内部类实现Parcelable中的Creator的接口

常见面试题

Parcelable和Serializable的区别和比较

Parcelable和Serializable都是实现序列化并且都可以用于Intent间传递数据,Serializable是Java的实现方式,可能会频繁的IO操作,所以消耗比较大,但是实现方式简单 Parcelable是Android提供的方式,效率比较高,但是实现起来复杂一些 , 二者的选取规则是:内存序列化上选择Parcelable, 存储到设备或者网络传输上选择Serializable

Android里面为什么要设计出Bundle而不是直接用Map结构

Bundle内部是由ArrayMap实现的,ArrayMap的内部实现是两个数组,一个int数组是存储对象数据对应下标,一个对象数组保存key和value,内部使用二分法对key进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,只适合于小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。而HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。因为使用Bundle的场景大多数为小数据量,我没见过在两个Activity之间传递10个以上数据的场景,所以相比之下,在这种情况下使用ArrayMap保存数据,在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用。

另外一个原因,则是在Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,HashMap使用Serializable进行序列化,而Bundle则是使用Parcelable进行序列化。而在Android平台中,更推荐使用Parcelable实现序列化,虽然写法复杂,但是开销更小,所以为了更加快速的进行数据的序列化和反序列化,系统封装了Bundle类,方便我们进行数据的传输。

Android中Intent/Bundle的通信原理及大小限制

Intent 中的 Bundle 是使用 Binder 机制进行数据传送的。能使用的 Binder 的缓冲区是有大小限制的(有些手机是 2 M),而一个进程默认有 16 个 Binder 线程,所以一个线程能占用的缓冲区就更小了( 有人以前做过测试,大约一个线程可以占用 128 KB)

为何Intent不能直接在组件间传递对象而要通过序列化机制?

Intent在启动其他组件时,会离开当前应用程序进程,进入ActivityManagerService进程(intent.prepareToLeaveProcess()),这也就意味着,Intent所携带的数据要能够在不同进程间传输。首先我们知道,Android是基于Linux系统,不同进程之间的java对象是无法传输,所以我们此处要对对象进行序列化,从而实现对象在 应用程序进程 和 ActivityManagerService进程 之间传输。

而Parcel或者Serializable都可以将对象序列化,其中,Serializable使用方便,但性能不如Parcel容器,后者也是Android系统专门推出的用于进程间通信等的接口